Swiftでは、手作業でモックオブジェクトを作成します。その設計がどのようにユニットテストを書けば良いかという方針を決めています。テストをより表現力豊かにするようモックオブジェクトをより強力にすることはできるでしょうか?モックライブラリから何を学ぶことができるでしょうか?OCMockitoというObjective-C製のモックライブラリを書いてきた私の経験をSwiftの手作りモックに応用してみます。
モックオブジェクトをより便利にする
アメリカン・エキスプレスのiOSエンジニアです。Swift環境でモックオブジェクトをどう作るのか
なぜ実オブジェクトの代わりに用意するのか?
コックは時間を掛けます。常にいるとは限りません。
UnitTestでは偽のコックを使いたいわけです。
料理をテストするのではなく、料理長にWaiterがどのように接したか、契約関係、インタラクションをテストします。
プロトコルを定義することで可能になります。
リアルオブジェクトでもFAKEオブジェクトでもプロトコルに適合していればどちらも使えるようになります。
ラーメンを作成するプロトコルがあるとします。
料理は時間が掛かるので通常コンプリーションハンドラーが必要ですが、とりあえず忘れて下さい。
これがWaiterの実装です。cookはイニシャライズ時に提供されます。
ここではとりあえず、注文メソッドをハードコーディングします。
味噌ラーメン2杯わかめと卵をトッピング。
どうテストするのか
モッククックを作ります。クックラーメンメソッドの実装ですが実際に料理はされません。
情報を取得します。どうやって呼び出されたかを確かめます。これはモックオブジェクトの重要なことです。
テストしたいメソッドを呼び出します。orderメソッドです。
最終的には何かをアサートします。
インタラクションテストでは情報の取得と取得した情報のアサーションに関係ができます。
呼び出されたかどうかをBoolで用意すると思います。これには課題が有ります。
こういう形にすると情報が保存されないんです。
見るだけではメソッドが呼び出されたことしかわかりません。これは課題になります。
複数呼び出されていると課題になる’
その代わりに何度、メソッドが呼び出されたかを記録しましょう
じっさいにリファクタリングで2回呼ばれるようになったことが有ります。
後々リファクタリングする際にも役割を果たします。
ここでも情報は保存されません。でも直近の引数のセットがあればいいでしょう。
そこでLastとして直近しか無いことを明示しましょう
これでテストが動くということです。
ここでは決まり事が有ります。UnitTestは一つしかアサーションは出来ないです。一つの真実しか述べられません。
ここでは4つの結果を述べています。
単独にした方がいいです。
これでエラーメッセージにどんな意味があるんでしょう
テストではなくHelperメソッドがポイントされます。 これは問題になります。特に二階別のところが呼んだりすると。
パラメーターで場所をわたしましょう。
Failしたものが示されるようになりました。
でも2が3ではないことしかわからない。
2は何で3は何なのか。
これでエラーメッセージが使えるものになりました。
ではトッピングの配列は?順番を変えてもテストが通ってほしいですね
これは脆いテストの例です。壊れてほしくないときにも壊れる。フラジャイルテスト
今日はPassしても明日は壊れるかもしれない。プロダクションコードの変更で壊れるかもしれない。
出来るならUnitTestをSaftyネットにして自信を持った変更をしたい。そうすると継続的なリファクタリングが可能になります。
そうするとコードが進化を続けるわけです。
コードが許す範囲でしか我々はAgileでいられません。
大切な重要なものに反応してもそれほど重要でないことは無視する。そういうものであってほしいわけです。
それではテストの脆さを弱めたいと思います。
重要なことだけを確認します。
パラメータを変えていきます。Boolを返します。
期待された値と実際の値をレポートします。
異なる配列のトッピングテストが失敗した場合
わかめでなく海苔を受け取ったら?
悪くはないが良くはないエラーメッセージです。
データ量が多くなるとどうでしょう、CIのログならどうでしょう。
より細かなアウトプットをしてくれるPredicateだといいですね
そこでHamcrest Matchers。あたいを評価するPredicateです。期待値を説明してくれます。ミスマッチがあったら、Matcherが細かくその期待値と何が違うかを説明してくれます。
MatcherからMatcherを作ることも出来ます。
使いやすくヘルパーを作ってみましょう
String配列マッチャーになります。
トッピングをchashuに変えてみましょう
期待値が説明されています。どのような順でもわかめと卵のは行ったものがほしいと書いてあります。細かくミスマッチの内容を説明している。若めなのにチャーシューが欲しかったと。
UnitTestの失敗から十分な情報を得ることができれば、テストメッセージから何が悪かったのかわかります。
全部にMatcherを使ったら
EqualToというマッチャーを使える
モックオブジェクトではイコールに制限されることはありません。
丼の数が2かそれより大きいことが出来ます。
最後の二つを特定する必要はない。
他のMatcherを使うことも出来る。Prefix確認したり。
Fakeオブジェクトは実際のオブジェクトが高価、遅い、信頼できない、グローバルステートを変えてしまう時に使う機会があります。
実際思った通りに呼ばれたかどうかを確認する時に使えるものになります。
より協力なモックオブジェクトを考えました。
Boolで呼ばれたかを釣らずにカウントしました。行数をパスしました。メッセージを加えました。Equal、Predicateをつかい柔軟性を高め脆さを軽減します。Hamcraster Matcherを使うことも考えて下さい。
目的はテストを行うだけではなく、リファクタリングをしてくことです。
重要なものに反応し、そうでないものは無視するものを作りたい。ガラスのように繊細なテストは必要ありません。テケのように強くしなやかなテストがほしいわけです。