3/3〜5の3日間、サイバーエージェントさんのセミナールームで開催されたtry! Swift、その3日目に行ってきました。 海外からも100人を超える方々が参加され、とても活気のあるカンファレンスでした。
Swift言語にフォーカスした本カンファレンスですが、3日目は開発者テストに関するセッションが2つありました。圧倒的にスイフト力が足りない私なので、これらのセッションについてだけ書きます。
Swiftにおける実践的なモック化について
Veronicaさんのセッション。Objective-Cは動的言語なので、テストコードでモック(を含む、テストダブル)を使ってプロダクトコードやフレームワークの挙動をランタイムに置き換えることができていました。またそれを容易に実現するためのOCMockというライブラリもありました。
しかしSwiftではこれができないため(反面、安全と言えます)、どうモックを使っていくべきか?というセッションです。
そもそも、モックを使う理由
続いて、DI(Dependency Injection: 依存性の注入)を使う理由
- カスタマイズ(置き換え)が容易にできる
- オーナーシップが明確になる
- テスタビリティ(試験性、テスト容易性)を上げる
テストダブルの代表的なもの
- スタブ(メソッドの戻り値を置き換える)
- モック(メソッドの呼び出し、引数を検証する)
- パーシャル・モック(特定のメソッドだけ置き換え。これは『xUnit Test Patterns』の定義・分類ではなく、恐らくOCMock独自の言葉)
パーシャル・モックのアンチパターン
- setUp()でモックを定義するのが大変になる
- What's real? What's fake?
モックを自分で作る場合、本物のサブクラスを作るのでなく、そもそも必要なものをProtocolとして定義しておく。Javaでも原則Interfaceを書くという宗派がありましたが、それ*1。
「ロールをモックせよ」の話。むやみにモック化しない。壊れやすいテストになる。
Swiftで将来的に使えるようになりそうな?モックフレームワークの紹介(リンクはぐぐったもの)
GitHub - rheinfabrik/Dobby: Swift helpers for mocking and stubbing
GitHub - DeliciousRaspberryPi/MockFive: A Mocking Framework for Swift Unit Tests
GitHub - mflint/SwiftMock: A mocking framework for Swift
GitHub - SwiftKit/Cuckoo: First boilerplate-free mocking framework for Swift!
CuckooはMockitoインスパイア系っぽい。
QA
- 日付や時間に依存するテストはどう書くべき? -> 遭遇したことがないけど、考えてみるのも面白そう
@niwatakoさんの書き起こしも参照。
所感
Objective-Cでは(OCMock/OCMockitoでは)依存オブジェクトの扱いが雑な設計であっても、ランタイムにモック化することで強引にテスト可能だったりしたのですが、Swiftではちゃんと設計しないとダメだよね、という話。
またSwiftに限らず、モックは便利さにつられて乱用しがち、そしてフラジャイルな(壊れやすい)テストになりがち、という点まで、短い時間で正しく警告されていてすばらしいセッションでした。
モックに頼らない、という点は、設計の見直しで回避できるものももちろんありますが限界もあるので、より上層の(結合度の高い)テストレベルで担保するなどが最適解かな、と考えています*2。このセッションではそこまで詰め込めなかっただけだと思う。
なお、Swiftの一部メソッドを(Objective-Cの機能を使って)動的に置き換える手段について、Danielさんの"Code Injection from scratch"セッションで触れられていました。こちらも@niwatakoさんの書き起こし参照。
An Artsy Testing Tour
Ashさんのセッション。Artsyではこれまで4つのアプリに対してテストを書いてきたが、それぞれアプローチが違うので紹介する。
前提として、
ひとつ目のアプリ
- テストは無かった。Apple TVローンチまで時間がなく、担当者一人で作ったもの。
- テストを書くべきか否か、バランスを判断。コードベースが小さいものだったので、ローンチを優先した。
ふたつ目のアプリ
- はじめテストは無かったが、コードベースが大きかったのでリグレッションテスト強化のため、後からテストを追加した。
- "Bus factor"*3防止のため、テストのドキュメントとしての役割を重視*4。
- DIを多く使った
- RSpec*5を使い、セットアップの共通しているテストはbeforeEachにまとめるなど、テストコードをリファクタリング。リーダブルなテストに。
- 余りにネストしたcontext*6はおかしいので、設計を見直す。十分に説明的な名前がつけられるか?
- テストはドキュメント
- 振る舞いをテストする。コードをテストするのではない。
みっつ目のアプリ
- これもはじめテスト無し、後で追加した。
- ネットワークアクセスのあるアプリで、開発者も多く実装も散らばっていた。
- はじめiPhone用、後にUniversal Appに。コンテキストが異なるので、一揃いのテストスイートを、iPhoneとiPadふたつの観点からテストを実施した。
- 大きなクラスからテストしたが、修正が入ると既存テストの書き換えも多発した。
- Snapshot Test。画面のスナップショットを撮ってpngで保存し、ピクセルベースでdiffを取り合否判定*7。pull-requestの中にスクリーンショットがあるのでレビューしやすい。
- 小さいクラスからテストすべき
- 追加したコードにはテストを書く
よっつ目のアプリ
- これは最初からテストを書いた。はじめてのSwiftアプリで手探り状態のところ、テストコードが拠り所になった。
- テストツールにはQuick*8を使用。
- Nimble*9というMatcherライブラリを使えば、assert()を、expect().to()形式で書けて可読性が高い。
- Arrayなども直接比較できる。拡張もできるので、Snapshot用のMatcherを作った。
QA
- TDDはハイコスト。クライアント側は手を抜いていいのでは?という意見があるが -> Swiftは型が強力なのでLLと比べてテストの重要性は低いが、でもコンパイラだけではチェックできない不具合を見つけられる。ドキュメントの価値もある。テスト大事。
- Snapshot Testはどのテストレベルでやっている? -> 決めかねている。End to Endでなくてもいい。
- TDDで「機能」を捉えるのが難しい -> TDDは賛否両論。Ashさんはどちらでもなくケース・バイ・ケース。繰り返しやっているとテスタブルなコードを書けるようになる。ロバストにはなりきらなくても、価値はある。小さく、小分けして、DI。繰り返しやってみること。練習である。
QAのメモはかなり怪しい。@niwatakoさんの書き起こしも参照。
所感
テストに対する姿勢、メリットが明確で、かつ4パターンの実際のアプリの事例を挙げられていることもあって、とても説得力のある、良いセッションでした。Spec BDD派が増えそう。
Spec BDDに限らず、可読性の高いテストはドキュメントとしても有効なので、ちゃんとメンテナンス、リファクタリングすべきですね。
また、質疑応答で出たTDDの話。プロダクトコードとテストコードを行き来することで、複数の視点でコードを見ることができるようになるはずなので、訓練だと思ってTDDをやってみるのは良いことだと思う。 後からテストを書くのは難易度が高いことが多いので、テストを書く習慣、テスタブルなコードを書く習慣をつけるのは良いこと。
カンファレンス全体を通して
先日のDroidKaigiもそうですが、開発者主体で、スポンサーも付けて、有料で、海外からも参加者およびスピーカーが来てくれる。ほんの2〜3年前には考えられなかったことで、それを実現された主催者・関係者の尽力はすごいと思います。
改めて、お疲れ様でした。ありがとうございました。
こうして(ごく一部のセッションの)ブログを書くことくらいしかできませんが、少しでも盛り上がりに貢献して、次回以降につながればいいな、と。
最後に蛇足ながら。Twitterで「本カンファレンスが1スレッド進行なのが良かった」という話がありました。完全に同意!なのですが、それは今の時期のSwiftだから、という条件付きかも。マルチセッションがすべて悪、みたいな方向には行かないで欲しいかな。
*1:Javaでは「C言語のヘッダファイルみたいに増える」という批判がありましたが、少し前までObjective-Cで書いていたから抵抗ないはず?
*3:日本だとトラックを使いますよね?
*4:いわゆる仕様化テスト
*5:と言っていたけど恐らくKiwiかな?
*6:describeと同義
*7:FacebookのOSSとのことなので、恐らく https://github.com/facebook/ios-snapshot-test-case を使用
*8:RSpec, Kiwiと同様、Spec BDDライブラリ。 https://github.com/Quick/Quick