単体テストの考え方/使い方
https://gyazo.com/2b116e16cf285accbb49e5d024999c27
なぜこの本を読んだか
何が書かれている本か
主な内容はもちろん単体テストの深い話だったりそのテクニックの話ではあるものの、結局よく巷で言われるように「テストの品質はプロダクションコードの設計に依存する」というところまで踏み込んでいて、後半は ヘキサゴナルアーキテクチャ だったり 関数型アーキテクチャ だったりの話題まで触れていたりする。 (とはいえその手の話はこの本だけでは十分理解できないので クリーンアーキテクチャ だったり、 ドメイン駆動設計 や 実践ドメイン駆動設計 だったり ソフトウェアアーキテクチャの基礎 だったりを読んだ方がいいが) ただしテストの部分だけでかなり良く言語化されていてノリで単体テストを書いてたり、自動テストをもっと良くしたいんだけどなあと思ってたり、自動テストのガイドラインを作っているような人には一読してもらいたい本であった。
メモ
それでは、単体テストをすることで何を成し遂げたいのでしょうか?その答えは、ソフトウェア開発プロジェクトの成長を持続可能なもににする、ということです。
確かに網羅率を計測することで価値のある情報を得られるのですが、その情報をもとにテスト・スイートの質を評価することはできません。... なぜなら、網羅率はテスト・スイートの質が悪いことを示せても、テスト・スイートの質が良いことを証明することはできないからです。
自分もカバレッジを計測することでカバーされていない行や分岐を見つけることだったり全体の傾向を掴むことは有用だけど、カバレッジそのものを追えば品質が測定できるとは思っていなかったので同感だった。
カバレッジを目標値にする話をよく見るが、多くはここで書かれているように「悪くはない」ことしか言っていないと考えていたのでこのあたり納得感高かった。
テスト・スイートの質
テストをすることが開発サイクルの中に組み込まれている。
これは現代においてはほぼ満たせそうな気がする
コードベースの特に重要な部分のみがテスト対象となっている。
プロダクションコードにも価値の違いがあるように、テストコードにも価値の違いがあってドメインモデルのような重要な箇所にこそ労力を割くべきというのはそうだと思った。(もちろん結合テストはむしろアプリケーションレイヤーだったりをテストすべきだと思うし、書籍もそう説明している)
最小限の保守コストで最大限の価値を生み出すようになっている。
価値のあるテスト・ケースを認識できること
退行 (regression) に対する保護
これによって
既存の機能の何らかの問題を持ち込んでしまった場合、テストによってそのことが早い段階で警告されるようになる
プロダクション・コードを変更しても退行が起こらないことに自信を持てるようになる
リファクタリングへの耐性
迅速なフィードバック
テスト・ケースを理解することがどのくらい難しいのか
テストを行うことがどのくらい難しいのか
保守のしやすさ
テスト・ケースは多いほど良いと思っている開発者も多く存在します。しかしながら、その考えは間違いです。なぜなら、コードは資産ではなく負債だからです。
価値のあるテスト・ケースを作成できること
1つのテスト・ケースに関する修正が他のテスト・ケースに影響を与えてはいけない
問題領域のことに精通している非開発者に対してどのような検証をするのかが伝わるような名前をつけること
コードや実装の詳細に基づく名前やケースを作ってしまうと実装の変更とともにテストが壊れてしまう
古典学派とロンドン学派
単体テストの定義には、2種類あって古典学派はいわゆるエクストリームプログラミングとかTDDとかそっち系でロンドン学派は単体テストは必ず1つのクラスについてのテストであるべきとしている考え方 (= そのクラス以外のものは全てテストダブルで置き換えられるべきという考え方)
https://gyazo.com/81049387f9d42ebf308bac956ce73b58
の図にもあるような整理でテストダブルの置き換え基準を持っている
古典学派は、逆に1つのクラスを起点としての1つの振る舞いのテストのことを対象とすべしと主張している
モックとスタブ
モックはテスト対象システムからその依存に向かって行われる 外部に向かう コミュニケーション (出力) を模倣し、そして、検証するのに使われる。このときモックが模倣するコミュニケーションはテスト対象システムが依存の状態を変えるために行うその依存への呼び出しのことになる。
スタブは、 依存からテスト対象システムに向かって行われる 内部に向かう コミュニケーション (入力) を模倣するのに使われる。このとき、スタブが模倣するコミュニケーションはテスト対象システムが依存からデータを取得するために行うその依存への呼び出しのことになる。
https://gyazo.com/5712da234b444333f41c5e23c72568e8
Mockito での例だと
code:kotlin
// stub (模倣部分)
when(mock.doSomethingA()).thenReturn(100)
// mock (検証部分)
verify(mock,times(1)).doSomethingB(20)
観察可能な振る舞いをテスト対象とすべき
テストは、観察可能な振る舞いに対して実行されるべき
クライアントが目標を達成するために使う公開された操作
計算をしたり、副作用を起こしたりするメソッド、もしくは、それらの両方を行うメソッドのこと
クライアントが目標を達成するために使う公開された状態
システムの現時点でのコンディションのこと
プライベートメソッドをテストしてはいけないなどの指針があるがこれは以下のような図で説明ができる
https://gyazo.com/99853b85a1b7b66f5b1c34f375594e90
理想的には、上の図のように観察可能な振る舞い = 公開されたAPI となるはずである。
しかし事情によっては、プライベートなAPIが観察可能な振る舞いの範囲に入ってしまうこともあり得る。
その場合には必ず、実装詳細が漏洩していないかどうかというのをまずプロダクションコードの設計レベルで疑う。
その上でどうしても観察可能な振る舞いにプライベートなAPIが入ってしまう場合に限ってはプライベートなAPIのテストは書いてもOKなので必ずしもプライベートメソッドのテストが常にNGなわけではない。
登場するテクニック
AAAパターン
お馴染みの Arrange, Act, Assert の3つにテストを整理する考え方。
本書の中では、 各ふぇー座右の頭にそのフェーズを示すコメントをつけるようにする のが良いと書いている。
例
code: kotlin
@Test
fun test() {
// Arrange
val target = A()
// Act
val actual = target.doSomething()
// Assert
Assert.equals(expected, actual)
}
Object Mother
ある特定のテスト対象のオブジェクトや協力オブジェクトを作るためのパターン
AAAパターンにおける Arrange が長くなってしまいがちなのでそこを綺麗にするテクニック
code:kotlin
@Test
fun test() {
// Arrange
val target = createSpecificTargetObject()
// ...
}
private fun createSpecificTargetObject(): A {
return A(...)
}
Test Data Builder
Object Mother に似ているが、もっと汎用性を持たせて全てのプロパティをビルダーで設定できるようにするパターン
Humble Object
現実には、どうしてもテストが難しい箇所というのがある。例えば利用しているプラットフォームやフレームワークの境界で Interface を挟んだりが難しくてテストダブルをコントロールするのが難しい場合など。
e.g. iOS の UIController とか Android の Activity とか バックエンドの HTTP Request 自体とか
そういう場合には Adapter みたいな層を設けて、なるべくロジックを含ませずにロジックだけをテストしやすいクラスなどに切り出すようにした上で難しい部分はテストをしないと割り切ることも重要。
こうしたロジックを持たないテストしづらいものを閉じ込めたものを Humble Object と呼ぶ。
世間で見る Presenter とかはこの Humble Object の要素を持っていることが多い気もする。
あとはバックエンドでいえば、 Controller とか。
感想
全体としては自分が感覚的に思ってたことや古い文献などから培った考え方とほとんど一致していたので安心できたというのがまずある。
その上で前提として単体テストの書き方だったり、良い単体テストや良い指針はかなり主観的になりやすい部分だと思うのでこうした形で指針を多く語ってくれている本が日本語で出たことはめちゃくちゃ良いことだと思う
自分のチームのテストガイドラインにもこの本を参照させていこうと思ったりした
やや気になったところは以下で注意が必要かもしれない
Regression の訳に「退行」をあてたのは読んでいて非常に気になってしまうので素直に「リグレッション」とかでもよかった気がする
この辺は訳に関係ない
「管理下」という言葉が頻繁に出てくるがおそらく under controll とかを訳したのだと思うがこの英語がそもそも様々なコンテキストを持つものなので特に後半部分でどの範囲を under controll としているのかは難しくて読み進めるのに苦労した部分があった
「単体テスト」と「結合テスト」という風に分けているにも関わらず「結合テスト」と 「E2Eテスト」の分類が途中に出てきたりとかは若干気になるところはあるがこの本はあくまでも単体テストの話として読めばそこまで気にはならない