JUnit5をmainから実行する
こんにちは。akaimoです。
今回はJUnit5をmain関数内で使用する方法です。
環境
- Java9
- JUnit5.1.0-M1
- Kotlin1.2
ソース
これでmainからJUnitを動かせます。詳しい解説は後ほど。
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder import org.junit.platform.launcher.core.LauncherFactory import org.junit.platform.launcher.listeners.SummaryGeneratingListener import org.junit.platform.engine.discovery.ClassNameFilter.* import org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage import java.io.PrintWriter fun main(args: Array<String>) { val launcher = LauncherFactory.create() val summary = SummaryGeneratingListener() launcher.registerTestExecutionListeners(summary) val request = LauncherDiscoveryRequestBuilder .request() .selectors(selectPackage("jp.hoge.piyo.test")) .filters(includeClassNamePatterns(".*")) .build() launcher.execute(request) summary.summary.printFailuresTo(PrintWriter(System.out)) summary.summary.printTo(PrintWriter(System.out)) }
これを実行すると次のようなログが出力されます。
Test run finished after 13255 ms [ 3 containers found ] [ 0 containers skipped ] [ 3 containers started ] [ 0 containers aborted ] [ 3 containers successful ] [ 0 containers failed ] [ 4 tests found ] [ 3 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ]
もしテストが失敗していたら、このログの前に失敗したテストと失敗した内容が表示されます。
詳細
testディレクトリ配下に入れIDEなどでテストを実行する場合はテストコードだけを書けば良いですが、main内で実行させる場合は全て自分で実行させなければいけません。
Launcher
JUnit5には、テストを検出したりフィルタリング、実行するためにLauncherというものを使用します。
まずはそのLauncherを作成します。
val launcher = LauncherFactory.create()
Summary
次はSummaryです。
これは名前の通り、テストの内容であったり結果を保持しています。
Summaryを作成して、Launcherに登録します。
val summary = SummaryGeneratingListener() launcher.registerTestExecutionListeners(summary)
Request
Requestでは実行するテストの選択やフィルタリングをします。
selectore
とfilters
を適宜変更して実行したいテストが実行できるようにします。
val request = LauncherDiscoveryRequestBuilder .request() .selectors( selectPackage("jp.hoge.piyo.test"), selectClass(MyTestClass::class.java) ) .filters(includeClassNamePatterns(".*Tests")) .build()
作成したrequestをLauncherに渡せばテストを実行できます。
launcher.execute(request)
結果の表示
最後にテスト結果を表示して終了です。
summary.summary.printFailuresTo(PrintWriter(System.out)) summary.summary.printTo(PrintWriter(System.out))
なお、summary.summary
よりデフォルトで出力される項目に個別にアクセスできるので、任意のフォーマットで出力することもできます。
react-reduxにflowtypeを導入しPropsに型を付ける
flowtypeを導入したとき、reactとreduxをつなぐ部分の情報が少なかったのでまとめます。
flowtypeの導入前
比較として導入前のソースを載せておきます。
import React, {PropTypes, Component} from 'react' import {connect} from 'react-redux' import * as actions from '../actions class SamplePage extends Component { render() { const {title, description, myAction} = this.props return ( <div> <button onClick={myAction}>{title}</button> <p>{description}</p> </div> ) } } SamplePage.propTypes = { title: PropTypes.string, description: PropTypes.string, myAction: PropTypes.func } const mapStateToProps = state => { return { title: state.title, description: state.description } } const mapDispatchToProps = dispatch => { return { myAction: () => dispatch(actions.myAction()) } } export default connect(mapStateToProps, mapDispatchToProps)(SamplePage)
導入後
これが導入後のソースです。propTypesが消えましたが、typeを記述しているので行数はあまり変わっていません。
import React, {Component} from 'react' import {connect} from 'react-redux' import * as actions from '../actions import type {MapStateToProps, MapDispatchToProps} from 'react-redux' type StateToProps = { title: string, description: string } type DispatchToProps = { myAction: () => void } type Props = StateToProps & DispatchToProps class SamplePage extends Component<void, Props, void> { render() { const {title, description, myAction} = this.props return ( <div> <button onClick={myAction}>{title}</button> <p>{description}</p> </div> ) } } const mapStateToProps: MapStateToProps<*, *, StateToProps> = state => { return { title: state.title, description: state.description } } const mapDispatchToProps: MapDispatchToProps<*, *, DispatchToProps> = dispatch => { return { myAction: () => dispatch(actions.myAction()) } } export default connect(mapStateToProps, mapDispatchToProps)(SamplePage)
ポイント
propTypes
が3つのtype
に置き換えられています。
その中で特に大事なところが以下で、
type Props = StateToProps & DispatchToProps
mapStateToProps
とmapDispatchToProps
で生成された2つのオブジェクトのtypeが合成されたものが、
class SamplePage extends Component<void, Props, void> { ...
となりcomponentに渡されていることを明示的に示すことができます。
補足1
MapStateToProps
は以下のように定義されています。
declare type MapStateToProps<S, OP: Object, SP: Object> = (state: S, ownProps: OP) => SP;
ジェネリクスで、SとOPを受け取ってSPを返す関数という型になってますね。
導入後のソースではMapStateToProps<*, *, StateToProps>
のように宣言しています。
Stateのtypeを宣言していれば、MapStateToProps<State, *, StateToProps>
となり、MapStateToProps
内のstateに対して型が適用されるのでSやOPに対しても型の宣言をすることをオススメします。
補足2
2018/02/01現在で最新のflowtypeではComponentに渡すPropsの型を以下のように記述します。
class SamplePage extends Component<Props> { ...
Travis CIでfastlaneを使用する
こんにちは。akaimoです。
Travis CIを利用しているiOSプロジェクトにfastlaneを導入して、xcodebuildを直接実行する環境から開放されたので、
備忘録としてやったことを残しておきます。
前提
- CI上でfastlaneを使用
- 複数人で開発
fastlaneのインストール
まずはfastlaneをインストールします。
fastlaneはgemで公開されているのでrubyの環境のバージョンを固定しておきましょう。
CIにインストールされているrubyのバージョンと同じにしておくことをオススメします。
違うとCI上でrubyのインストールが始まってしまうかもしれません。無駄な時間なので素直に同じバージョンを使いましょう。
$ rbenv local 2.4.2
こんどはfastlaneのバージョンを固定するためにbundler
をインストールします。
$ gem install bundler
Gemfile
に以下を記述します。
source 'https://rubygems.org' gem 'fastlane'
インストールします。
$ bundle install --path=vendor/bundle
ポイントとしては --path
を指定することでローカルの環境を汚すことなくfastlaneをインストールできます。
ここでインストールされたディレクトリはgitで管理する必要はないので.gitignore
に入れておきましょう。
セットアップ
xcodeprojがあるディレクトリでinitコマンドを実行して雛形を作成します。
$ bundle exec fastlane init
実行するとlane(1つのコマンドで実行されるアクションをまとめたもの)を作成するか聞かれます。2か3を選ぶと質問に答えるだけでitunes connectの設定がされるのでオススメです。
完了するとfastlane
というディレクトリが作成されています。
ファイルの中は以下のようになっています。
Fastfile
- レーンを記述します。initコマンドで選択したlaneが作成されています。
Appfile
- fastlane全体で使用する情報を記載します。initで2か3を選んでいるとapple idやteam idなどが記載されています。
iTunes Connect(TestFlight)へのアップロード
TestFligntへアップロードします。
プロジェクトの設定によって変わりますが、基本は以下のような形です。
lane :beta do build_app( workspace: "Hoge.xcworkspace", scheme: "Hoge", output_directory: "./tmp" ) upload_to_testflight(ipa: "./tmp/Hoge.ipa") end
lanebeta
を実行します。
$ bundle exec fastlane beta
initのときに2か3を選択した場合は、initで入力した情報がキーチェーンに保存されているのでローカル環境ではアップロードできますが、
CI上だとapple idのパスワードが存在せず失敗してしまいます。
このような時のためにfastlaneはFASTLANE_PASSWORD
という環境変数からパスワードを取得してログインするようになっています。
.travis.yml
で環境変数にパスワードをセットしましょう。
この記事が参考になります。
これでCI上からアップロードできるようになります。
HockeyAppへのアップロード
次はHockeyAppへアップロードします。
HOCKEY_API_TOKEN = ENV['HOCKEY_APP_TOKEN'] PLIST_PATH = "path/to/Info.plist" build = get_info_plist_value(path: PLIST_PATH, key: "CFBundleVersion") version = get_info_plist_value(path: PLIST_PATH, key: "CFBundleShortVersionString") lane :alpha do build_app( workspace: "Hoge.xcworkspace", scheme: "Hoge", configuration: "Adhoc", export_method: "enterprise", output_directory: "./tmp" ) hockey( api_token: HOCKEY_API_TOKEN, bundle_short_version: version, bundle_version: build, ipa: "./tmp/Hoge.ipa", dsym: "./tmp/Hoge.dSYM.zip", ) end
先程と同じようにapiのtokenを環境変数にセットし、それを読み込みます。
TestFlightへのアップロードができていれば、同じようにできるはずです。
Unit Testの実行
せっかくCIの環境があるので、テストも実行しておきましょう。
lane :test do run_tests( workspace: "Hoge.xcworkspace", scheme: "Hoge", configuration: "Debug", device: "iPhone 8" ) end
Slackへの通知
fastlaneにはslackに通知を送る機能があり、自由にカスタマイズでき、好きなタイミングで送ることができます。
今回はアップロードが完了したら通知するようにします。
まずは通知用のlaneを作成します。
lane :hockey_notification do link = lane_context[SharedValues::HOCKEY_DOWNLOAD_LINK].to_s name = sh("echo $USER") slack( slack_url: "https://hooks.slack.com/services/", channel: "#general", success: true, default_payloads: [], attachment_properties: { title: "Uploaded to HockeyApp", text: "Beta version is now available for <" + link + "|download>.", fields: [ { title: "Build by", value: name, short: true }, { title: "Build Date", value: Time.new.to_s, short: true } ] } ) end
これを実行すると、このような通知がきます。
slackのattachment apiをカバーしているので、公式のドキュメント通りにカスタマイズできます。
これを先程のHockeyAppへアップロードするlaneに追加します。
HOCKEY_API_TOKEN = ENV['HOCKEY_APP_TOKEN'] PLIST_PATH = "path/to/Info.plist" build = get_info_plist_value(path: PLIST_PATH, key: "CFBundleVersion") version = get_info_plist_value(path: PLIST_PATH, key: "CFBundleShortVersionString") lane :alpha do build_app( workspace: "Hoge.xcworkspace", scheme: "Hoge", configuration: "Adhoc", export_method: "enterprise", output_directory: "./tmp" ) hockey( api_token: HOCKEY_API_TOKEN, bundle_short_version: version, bundle_version: build, ipa: "./tmp/Hoge.ipa", dsym: "./tmp/Hoge.dSYM.zip", ) hockey_notification end
alpha
laneを実行するとアップロード完了後にslackへ通知がきます。
おわりに
fastlaneを使用してCI上の処理を簡略化しました。
xcodebuildを直接実行していたときと比較すると、
- CIのコードが大幅に減った
- 出力に色が付き整形されるので見やすい
ことがすぐに感じられたメリットです。
Xcodeのバージョンアップによるxcodebuildの変更への追随が、fastlaneの更新だけで済むようになればいいな
と期待しながらCIを拡充していきたいと思います。
ScrollViewとStackViewを組み合わせる
akaimoです。
そろそろiOS8のサポートが終わってきたと思うので、TableViewの変わりにStackViewを使う方法を書きます。
使いにくいScrollView
スクロールが必要な複雑な画面の場合、同じ要素が1つも存在しないのにTableViewを使うことがありました。
ScrollViewではなく、TableViewを使う理由はいくつかありますが、
一番大きな理由はScrollViewを使うためのハマりポイントがたくさんあったからだと思います。
そんなScrollViewですが、StackViewと組み合わせて使うことで大幅に使いやすくなります。
ScrollViewの設定
storyboardとxibを使用して実装します。
storyboard
シンプルにいくために、UIViewControllerを継承したcontrollerを用意します。
このcontrollerをstoryboardで設定していきます。
- このcontrollerにScrollViewを設置し、alignを0で四カ所に制約を付けます。
- ScrollViewの上にStackViewを設置し、こちらもalignを0で四カ所に制約を付けます。
- ScrollViewとStackViewを選択し、Equal Widthの制約を付けます。
これで基本的な設定は完了です。
StackView
StackViewの設定をします。
- AxisをVertical
- AlignmentをFil
- DistributionをEqual Centering
- Spacingを0
view
複数の要素から構成されるので、カスタムviewを使うことをオススメします。
xibを使用してviewを構成します。
viewはいつも通りに作成します。注意するところとしては、このview全体のサイズが決まるように制約を付けることです。
これを守ればあとはStackViewがそのサイズを確保してくれるので、TableViewのように様々なviewを組み合わせてScrollViewを使用することができると思います。
VimにRustの環境を構築する
RustのIntroductionをやってみる途中で、補完とかが効かないとツライなと思ったので
VimにRustの環境を作ってみた。
前提
OSはMacです(たぶんLinuxでも動くと思います)
rustupでRustをインストールしていることを前提とします。
できるようになること
インストール
cargoでRacer(コード補完)とrustfmt(フォーマッタ)をインストールします。
cargo install racer cargo install rustfmt
Vim Plugin
このPluginを使います。
.vimrc
私の設定を乗せておきます。
使用していない機能もあるので、詳しくは各Pluginのドキュメントを参照してください。
NeoBundle 'racer-rust/vim-racer', {'autoload': {'filetypes': ['rust']}} let s:hooks = neobundle#get_hooks("vim-racer") function! s:hooks.on_source(bundle) let g:racer_cmd = "$HOME/.cargo/bin/racer" endfunction NeoBundle 'rust-lang/rust.vim', {'autoload': {'filetypes': ['rust']}} let s:hooks = neobundle#get_hooks("rust.vim") function! s:hooks.on_source(bundle) " save時にオートフォーマットする let g:rustfmt_autosave = 1 let g:rustfmt_command = '$HOME/.cargo/bin/rustfmt' endfunction NeoBundle 'scrooloose/syntastic', {'autoload': {'filetypes': ['rust']}} let s:hooks = neobundle#get_hooks("syntastic") function! s:hooks.on_source(bundle) " save時にシンタックスチェックする let g:syntastic_mode_map = { 'mode': 'passive', 'active_filetypes': ['rust'] } let g:syntastic_rust_checkers = ['rustc', 'cargo'] endfunction
この設定でsave時にオートフォーマットとシンタックスチェックが走ります。
コード補完はC-xC-o
でできます。
余談的な
これで結構快適にRustが書けるようになります。
大規模のソースを読むとなると、タグジャンプとかしたくなる。
もう少しRustを書くようならタグジャンプの設定もしようかな。
mackerelでhubotを監視する
raspberry piでhubotを運用していると、いつの間にかhubotが突然死していることがあります。
botが動いてくれないと色々と困るので、先日導入したmackerelで監視して、死亡したらslackにアラートが飛ぶようにします。
観測用のスクリプトを作成
mackerelでカスタムな値を監視するには、自分で監視用のスクリプトを用意する必要があります。
#!/bin/bash name="process.count.hubot" monitor_time=`date +%s` count=`ps aux | grep node | grep hubot | grep -v grep | wc -l` echo -e "${name}\t${count}\t${monitor_time}"
hubotが生きている = hubotのプロセスが存在する。
と定義して、hubotのプロセス数をmackerelに監視させます。
細かな仕様は以下を参照してください。
ホストのカスタムメトリックを投稿する - Mackerel ヘルプ
mackerel-agent.confに設定
先ほど作成したスクリプトをmackerelに読み込ませます。
mackerel-agent.confに以下のように追記します。
[plugin.metrics.process] command="/hoge/huga/process.sh" type="metric"
Agentの再起動
変更を反映させるために再起動します。
$ sudo service mackerel-agent start
以上でhubotの監視は完成です。
あとは、mackerelでアラートと通知先の設定をするだけです。
Raspberry PI 2にLet's Encryptとnginxでhttps環境を構築
Let's Encryptを使ってhttps環境を構築してみたいと思います。
最新のnginxをインストール
せっかくなので、最新のnginxをインストールしてhttp2にも対応させたいと思います。
基本的には以下の記事を見ればできます。
私の環境では、nginxの設定ファイルは/etc/nginx/conf.d/*****.conf
としました。
以下では、この記事ではあまり触れられていないLet's Encryptのセットアップを中心に書いていきます。
Let's Encryptのインストール
まずは、Let's EncryptをGitHubからクローンしてきます。
git clone https://github.com/letsencrypt/letsencrypt cd letsencrypt
証明書の発行
上記の記事ですでにnginxのセットアップは完了しているので、--webroot
オプションを使って
サーバーを起動したまま取得します。
./letsencrypt-auto certonly --webroot -d ${DOMAIN} --webroot-path ${WEBROOT}
-d
オプションをつけるとマルチドメイン証明書となるので、複数のドメインを指定できます。
${WEBROOT}
にはnginxで指定したドキュメントルートを指定します。
成功したら、以下のような出力があります。
IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at /etc/letsencrypt/live/******.**/fullchain.pem. Your cert will expire on 2016-**-**. To obtain a new version of the certificate in the future, simply run Let's Encrypt again. - If like Let's Encrypt, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le
この後、nginx -s reload
でnginxを再起動したらhttpsができるようになっているはずです。
以上で完成です。
ずいぶんお手軽にhttpsが利用できるようになりました。
これを機に個人のwebサーバーもhttpsで運用していきたいところです。