関数を完全にテスト可能にするためのものが2つあります。作用の分離と共作用を表面化です。この2つの側面の背景にある理論を探り、どのようにすればテスト容易なコードに導けるかを示します。また、最近オープンソース化されたコードベースを基にKickstarterで我々がどのように実践しているかも紹介します。
テスト可能なコードを書くということの2つの側面
テストについて深く話します
テスト可能なコードを書くには?それは表裏をなすものです。
そして関数言語を例にとって、キックスターターで我々がやっていることをお話します。OSSにしたのでみてもらえます。
Contact Me!
なぜテストをするか。経験と方法について知ることが必要
潜在バグを潰せたりして良い。先人たちが色々言っている。
私自身コードを書く時に関数的な書き方で書くことは良い試練だと思う。
実装のためにコードを書くのではなくて、テストはドキュメント化して、アプリケーションがどう動くかの期待値を買いて、エッジケースも考慮に入れていくことが重要。
シンプルな関数でもテストが難しい場合がある。
この関数はディスクからファイルを読んで内容を解釈して数値を返します。
コンソールに何かプリントすることが要件だとすると、このためにデバッギングをしたりはしない。
かなり簡単に導入できると思います。
このようなコードは書いたことがあると思います。ディスクやキャッシュやデフォルトから取得したりすると思います。でもテストする時より考える必要が有ることがあります。
シンプルに見えるがこれだけではすまない。number.txt以上のものが必要でグローバル関数を使う
String(contentOfFile:)
も利用する。これは現在のハードドライブの状態に依存する。
バンドルのパスを取得するGlobal関数はこの関数に関して皆さんが気付いていないような隠れたインプットもあります。
またその結果が他の世界の状態に影響を与えるということが有ります。
この関数がこういうことをしているというのは気が付かないわけです。
この関数をテストするにはファイルを特定のロケーションに置かなくてはいけないわけです。メッセージがコンソールに出されるということも確認しなければならないです。そしてコンピュータの他の部分には影響を与えないことを確認しなければいけません。
明示的なものと暗黙的なものが入出力にそれぞれあります。それを理解する必要があります
アウトプットの側面
アウトプットのテストが難しいのは副作用の存在です。 実行によって外の世界に観測可能な変化が起きるということです。
テストを実行して、実行したあとの世界の状態を確認する必要があるわけです。
その他の副作用が起きているかを確認するのは難しい。
どのように副作用を扱うか
副作用をなるべくコードの境界付近、決まったところへ動かしていくところが必要です。
あまり考えていないインタープリタが仕事をしてしまうことになります。
printするのをやめて戻り値を返すことにします。
この関数の複合がどのように壊れるかということを通常考えなければいけません。
アウトプットは慣れているのは簡単ですが次にインプットに行きます
インプット
完全にコントロールされて明確に定義されたデータ、世界の状態が分からなければなりません。
共作用によってインプットのテストが難しくなります。
インプットのテストは共作用のテストになります。
火を使って説明します。
DependencyのInjectionという人もいるかもしれません。
ある特定の世界の状態がなければ実行できないということです。
テストのためには特定の世界の状態を作らなければなりません。
やり方は、
コリンバレットが全てのGlobalを取り入れる、ひとつのstructの中に全て入れてしまうかんきょうにしてしまうという方法を述べています。
クッキーストレージを入れてしまったり、現在のログインユーザーも入れたり
日付に対するアクセスは、コードの中で非常に大きな共作用になります。
毎回違う値が帰ってきます。コードの中でDateを使うとそれは純粋な関数ではない。毎回条件が変わってしまいます。
テストの場合にはDateの振る舞いをコントロールするためにプロトコルにしてしまいます。
それからlanguageも
Reachabilityも。
次に、ReactiveSwiftを使っています。Schedulerを使ってコントロールしています。信号の遅延などに時間を使う。
UserDefaultsもCo-effectです。このように延々とリストは続きます。
グローバル関数を一箇所にまとめて必ずそこを通すと楽になります。
成否だけを確認しましょう
純粋な関数になりました。複雑性を関数のシグネチャの中に入れています。これは良いことです。どれぐらいの作業を関数がやっているのかが分かるようになります。
Selfドキュメンテーションという形になります。
ここから結論です。
共作用と副作用でテストが難しくなります。
共作用にたいしてはGlobalな関数に直接アクセスしなくて良いようにする
Q&A
テストに関して気に留めているという話がありました。テストは前によくやるのかあとによくやるのか
テスト駆動でやっています。失敗したテストを書き留めてやるという形になります。
Environmentをチューニングするのにリーダーモナドやステーともなどは使ったりしますか、プロパティベースのUnitTestを行ったりしますか
はい、我々の環境はとても簡単です。モナドなど、ギャップエンバイロメントはCoモナドになっています。あまり考えずにストレートな環境を考えています。プロパティベースはやりたいがやっていないです。