読者です 読者をやめる 読者になる 読者になる

モックオブジェクトをより便利にする | try! Swift Tokyo 2017 #tryswiftconf Day2-11 聞き起こし

try! Swift Tokyo 2017

twitter.com

Swiftでは、手作業でモックオブジェクトを作成します。その設計がどのようにユニットテストを書けば良いかという方針を決めています。テストをより表現力豊かにするようモックオブジェクトをより強力にすることはできるでしょうか?モックライブラリから何を学ぶことができるでしょうか?OCMockitoというObjective-C製のモックライブラリを書いてきた私の経験をSwiftの手作りモックに応用してみます。

モックオブジェクトをより便利にする

f:id:niwatako:20170303154225j:plain

アメリカン・エキスプレスのiOSエンジニアです。Swift環境でモックオブジェクトをどう作るのか

f:id:niwatako:20170303154247j:plain

なぜ実オブジェクトの代わりに用意するのか?

f:id:niwatako:20170303154316j:plain

コックは時間を掛けます。常にいるとは限りません。

UnitTestでは偽のコックを使いたいわけです。

料理をテストするのではなく、料理長にWaiterがどのように接したか、契約関係、インタラクションをテストします。

f:id:niwatako:20170303154410j:plain

プロトコルを定義することで可能になります。

リアルオブジェクトでもFAKEオブジェクトでもプロトコルに適合していればどちらも使えるようになります。

ラーメンを作成するプロトコルがあるとします。

f:id:niwatako:20170303154446j:plain

料理は時間が掛かるので通常コンプリーションハンドラーが必要ですが、とりあえず忘れて下さい。

f:id:niwatako:20170303154511j:plain

これがWaiterの実装です。cookはイニシャライズ時に提供されます。

ここではとりあえず、注文メソッドをハードコーディングします。

味噌ラーメン2杯わかめと卵をトッピング。

どうテストするのか

f:id:niwatako:20170303154548j:plain

モッククックを作ります。クックラーメンメソッドの実装ですが実際に料理はされません。

情報を取得します。どうやって呼び出されたかを確かめます。これはモックオブジェクトの重要なことです。

f:id:niwatako:20170303154624j:plain

テストしたいメソッドを呼び出します。orderメソッドです。

最終的には何かをアサートします。

インタラクションテストでは情報の取得と取得した情報のアサーションに関係ができます。

f:id:niwatako:20170303154715j:plain

f:id:niwatako:20170303154719j:plain

呼び出されたかどうかをBoolで用意すると思います。これには課題が有ります。

こういう形にすると情報が保存されないんです。

見るだけではメソッドが呼び出されたことしかわかりません。これは課題になります。

複数呼び出されていると課題になる’

その代わりに何度、メソッドが呼び出されたかを記録しましょう

f:id:niwatako:20170303154834j:plain

じっさいにリファクタリングで2回呼ばれるようになったことが有ります。

後々リファクタリングする際にも役割を果たします。

f:id:niwatako:20170303154904j:plainf:id:niwatako:20170303154911j:plainf:id:niwatako:20170303154919j:plain

f:id:niwatako:20170303154933j:plain

ここでも情報は保存されません。でも直近の引数のセットがあればいいでしょう。

f:id:niwatako:20170303154952j:plain

そこでLastとして直近しか無いことを明示しましょう

f:id:niwatako:20170303155004j:plain

これでテストが動くということです。

ここでは決まり事が有ります。UnitTestは一つしかアサーションは出来ないです。一つの真実しか述べられません。

ここでは4つの結果を述べています。

単独にした方がいいです。

f:id:niwatako:20170303155055j:plain

f:id:niwatako:20170303155104j:plain

これでエラーメッセージにどんな意味があるんでしょう

f:id:niwatako:20170303155122j:plain

テストではなくHelperメソッドがポイントされます。 これは問題になります。特に二階別のところが呼んだりすると。

f:id:niwatako:20170303155155j:plain

パラメーターで場所をわたしましょう。

f:id:niwatako:20170303155208j:plain

Failしたものが示されるようになりました。

でも2が3ではないことしかわからない。

2は何で3は何なのか。

f:id:niwatako:20170303155230j:plain

f:id:niwatako:20170303155244j:plain

これでエラーメッセージが使えるものになりました。

f:id:niwatako:20170303155258j:plain

ではトッピングの配列は?順番を変えてもテストが通ってほしいですね

f:id:niwatako:20170303155316j:plain

これは脆いテストの例です。壊れてほしくないときにも壊れる。フラジャイルテスト

今日はPassしても明日は壊れるかもしれない。プロダクションコードの変更で壊れるかもしれない。

出来るならUnitTestをSaftyネットにして自信を持った変更をしたい。そうすると継続的なリファクタリングが可能になります。

そうするとコードが進化を続けるわけです。

コードが許す範囲でしか我々はAgileでいられません。

大切な重要なものに反応してもそれほど重要でないことは無視する。そういうものであってほしいわけです。

それではテストの脆さを弱めたいと思います。

f:id:niwatako:20170303155457j:plain

f:id:niwatako:20170303155533j:plain

重要なことだけを確認します。

f:id:niwatako:20170303155550j:plain

パラメータを変えていきます。Boolを返します。

f:id:niwatako:20170303155607j:plain

期待された値と実際の値をレポートします。

f:id:niwatako:20170303155631j:plain

異なる配列のトッピングテストが失敗した場合

f:id:niwatako:20170303155649j:plain

わかめでなく海苔を受け取ったら?

f:id:niwatako:20170303155707j:plain

悪くはないが良くはないエラーメッセージです。

データ量が多くなるとどうでしょう、CIのログならどうでしょう。

より細かなアウトプットをしてくれるPredicateだといいですね

f:id:niwatako:20170303155748j:plain

そこでHamcrest Matchers。あたいを評価するPredicateです。期待値を説明してくれます。ミスマッチがあったら、Matcherが細かくその期待値と何が違うかを説明してくれます。

MatcherからMatcherを作ることも出来ます。

f:id:niwatako:20170303155924j:plain

使いやすくヘルパーを作ってみましょう

f:id:niwatako:20170303155955j:plain

String配列マッチャーになります。

f:id:niwatako:20170303160009j:plain

f:id:niwatako:20170303160016j:plain

f:id:niwatako:20170303160027j:plain

トッピングをchashuに変えてみましょう

f:id:niwatako:20170303160047j:plain

期待値が説明されています。どのような順でもわかめと卵のは行ったものがほしいと書いてあります。細かくミスマッチの内容を説明している。若めなのにチャーシューが欲しかったと。

UnitTestの失敗から十分な情報を得ることができれば、テストメッセージから何が悪かったのかわかります。

f:id:niwatako:20170303160148j:plain

全部にMatcherを使ったら

EqualToというマッチャーを使える

f:id:niwatako:20170303160214j:plain

モックオブジェクトではイコールに制限されることはありません。

丼の数が2かそれより大きいことが出来ます。

f:id:niwatako:20170303160238j:plain

f:id:niwatako:20170303160258j:plain

最後の二つを特定する必要はない。

他のMatcherを使うことも出来る。Prefix確認したり。

f:id:niwatako:20170303160327j:plain

Fakeオブジェクトは実際のオブジェクトが高価、遅い、信頼できない、グローバルステートを変えてしまう時に使う機会があります。

実際思った通りに呼ばれたかどうかを確認する時に使えるものになります。

より協力なモックオブジェクトを考えました。

f:id:niwatako:20170303160417j:plain

Boolで呼ばれたかを釣らずにカウントしました。行数をパスしました。メッセージを加えました。Equal、Predicateをつかい柔軟性を高め脆さを軽減します。Hamcraster Matcherを使うことも考えて下さい。

f:id:niwatako:20170303160512j:plain

目的はテストを行うだけではなく、リファクタリングをしてくことです。

重要なものに反応し、そうでないものは無視するものを作りたい。ガラスのように繊細なテストは必要ありません。テケのように強くしなやかなテストがほしいわけです。

f:id:niwatako:20170303160555j:plain