第Ⅱ部 合言葉で覚えるユニットテスト
テストの生産性を上げるためのガイドラインの章
5章 First(良いテストとは)
よいテストはFIRSTである
- Fast(迅速)
- Isorated(隔離)
- Repeatable(繰り返し可能)
- Self-validating(自律的検証)
- Timely(タイムリー)
Fast(迅速)
- 高速に実行できること。(1日に2〜3回実行するのが苦痛であれば、見直そう)
- 遅いコードへの依存を減らす
- DB等低速なリソースにアクセスをしなくてもいいよう設計する
Isolate(テストを隔離する)
- それぞれのテストが独立して振る舞い、規則正しく機能すること
- テストが失敗する理由が複数ある場合、分割することを検討する
- 時期や順序に依存しない
Repeatable(繰り返し可能)
- 実行するたびに同じ結果が得られること
- 繰り返しができないテストは、テスト対象のバグではなく自身に起因することが多い
Self-Validating(自律的検証)
- テスト自身の中で検証を行うべきである
- セットアップもテスト自体が行う
- InfinitestやCIの利用を検討する
Timely(適切なタイミングでテストする)
- チェックイン前、できればコードを書く前にテストを書くべき
- 古いバグのないコードに対してテストを書くのは無駄になりやすいので、問題になっている箇所、頻繁に変更される箇所に注力する
6章 Right-BICEP(テストの対象)
どのような部分にテストが必要か?
Right
結果は正しいか?
Boundary
境界条件が適切か?
Inverse
逆の関係はチェックできているか?
Cross-check
別の方法を使って結果をチェックできるか?
Error
エラーの条件を適切に発生させることができるか?
Performance
パフォーマンスの特性は許容範囲内か?
7章 CORRECT(境界条件の扱い)
Conformance(適合)
値は期待される形式に適合しているか?
Ordering(順序)
値の集合は適切な順序に並び替えられているか?
Range(範囲)
値は最小値と最大値の範囲内にあるか?
制約をチェックするマッチャー
org.hamcrest. TypeSafeMatcher<マッチング対象のクラス>を継承したクラスを使用してカスタムマッチャーを作成する。
https://gist.github.com/ftsan/7f464eab71f3e6c14add
Reference(参照)
自身が値をコントロール出来ない外部のコードを参照していないか?
- 参照先が有効範囲の外にある場合どうなるか?
- 外部への依存はあるか?
- 特定の状態のオブジェクトに依存しているか?
- その他に何かの必須の条件はあるか?
Existance(存在)
値が存在するか?(null, 0, 空集合なども含む)
Cardinality(要素数)
十分な個数の値が用意されているか?
Time(時間)
全ての出来事は一定の順序で発生するか?また、それぞれは適切なタイミングで発生し、想定される時間内に終了するか?
第Ⅲ部 より大きな設計の全体像
8章 クリーンなコードを目指すリファクタリング
リファクタリングとは
リファクタリング
機能面での振る舞いを変えずにコードの構造を変えること
メソッドを抜き出す(事前のリファクタリング)
複雑な処理を別メソッドに置き換える
※IntelliJでは[option + command + m]でメソッドの抽出を行える
メソッドの置き場を決める
メソッドを本来あるべき場所(クラス)に置き換える。
※IntelliJでは[F6]でメソッドの移動を行える
デメテルの法則
別のオブジェクトへと連鎖するようなメソッド呼び出しを避ける。
a.getA().getB()みたいなやつを避ける。
参考: 何かのときにすっと出したい、プログラミングに関する法則・原則一覧
リファクタリングの自動実行と手動実行
インライン化
メソッドの中身を呼び出し元に直接記述すること。
※ IntelliJでは[option + command + n]で実行する。
過剰なリファクタリングの是非
メリット
適切に設計されたメソッドは、読みやすく、個別のテストが可能である。
パフォーマンス面の不安
最適化を求められる可能性があるなら、クリーンな設計を行う。
最適化されたコードは読みにくい場合が多く、また保守のコストが高く、柔軟性が低い場合が多い。
クリーンな設計であれば、パフォーマンスの最適化が求められたとしても容易に対処できる。
第9章 より大きな設計上の課題
ProfileクラスとSRP
クラス設計での原則群SOLID
名称 | 意味 |
---|---|
SRP(Single Responsibility Principlie、単一責任の原則) | クラスを変更する理由は一つだけであるべき。クラスは目的を1つだけ持つようにする。 |
OCP(Open-Closed Principle、開放と閉鎖の原則) | 拡張に対しては開放的で、変更に対しては閉鎖的であるよう設計すべき。 |
LSP(Liskov Subsititution Principle、リスコフの置換原則) | 派生型は基本形を置き換え可能であるべき。クライアントの観点からは、オーバーライドされたメソッドが従来の機能を壊してはならないという意味。 |
ISP(Interface Segregation Principle、インターフェースの分離原則) | クライアントが利用しないメソッドに対して依存関係を持つべきでない。大きなインターフェースは分割し、小さな複数のインターフェースを持つようにする。 |
DIP(Dependency Inversion Pinciple、依存関係逆転の原則) | 上位のモジュールは下位のモジュールに依存すべきではない。全てのモジュールは抽象に対して依存し、実装の詳細に依存してはならない。 |
新しいクラスの抜き出し
実世界での物事にマッチしているからといって、1つのクラスで全てを処理しようとすると、複雑で再利用が難しい設計になりがちである。
具体的なものでなく、概念と対応した形でクラスを定義する。
コマンドとクエリの分離
コマンドとクエリの分離原則
メソッドはコマンド(副作用を生むような何らかの処理)を実行してもよいしクエリ(戻り値の要求)に応答してもよいが、両方を同時に行ってはならない
ユニットテストを保守するコスト
- システムの設計やコードの質が低いと、ユニットテストの保守にかかるコストは増大する。
- コマンドとクエリの分離によってクラスを分割によってprivateメソッドをpublicメソッドへと移し替える際に、テストが不十分であったことに気づくはずである。メソッドをpublicに変更した際に、そこで公開されるふるまいのドキュメントとしてのテストを作成する。
その他の設計上のポイント
- コンストラクタで行う処理は最低限に留める。
- 実装の詳細が複数のクラスにまたがっている場合に注意。(Shotgun Surgery,散弾銃を使った手術の意)
- システムの設計を常に監視する。
- 最善の設計は1つではない。
まとめ
- ユニットテストのカバー範囲を広げ、確信を持って設計を改善し続けられるようになろう。
第10章 モックオブジェクト
テストでの課題
- HTTP通信など、外部への実際の呼び出しは低速である。
- 外部のAPIが常に利用可能かどうかはわからない。
厄介な振る舞いをスタブに置き換える
テスト向けの設計変更
テストのためだけに設計の変更を行うことは悪いことではなく、むしろよいことである。
スタブを賢くする(パラメータの検証)
モックツールを使ったテストの簡素化
注入ツールを使った簡素化
Mckito
モック作成用のライブラリ。入力値のチェックや、特定のメソッドが呼ばれたか等の検証もできる。
また、簡素なDI機能も組み込まれており、実際のコードに手を入れずにモックへの差し替えができたりもする。
モックを利用する際のポイント
- 処理の内容を明確に表現すること。
- モックは実際の振る舞いを置き換えているため、置き換えても問題無いか注意すること。
- 実運用のデータではなく、テストデータだとわかるようなデータを使うこと。
- 実運用のコードではないため、テストのカバー範囲に漏れが発生する点に注意すること。
まとめ
ユニットテストでは外部のリソースに依存するようなテストは実施しなくてもよい。モックを活用すること。
第11章 テストのリファクタリング
不必要なコード
- 無意味なtry/catchは避ける
-
無意味なnullチェックを避ける
アブストラクションの欠如
アブストラクション
重要な概念が目立っており、不必要な細かい事柄は隠されているコードのこと。
良いテストは、クライアントとシステムのやり取りを表現したアブストラクションになっている。 カスタムマッチャーを利用して、詳細を隠蔽する。
無関係な情報
リテラルを避け、定数を使う。また、関心の無いデータは空文字として表現してもよい。
肥大化したコンストラクタ
ヘルパーメソッドを用意し、本質的でない実装を隠蔽する。
複数のアサーション
1つのテストにアサーションを1つだけにすると、テストの名前をわかりやすくできる。
不必要な詳細さ
ログの停止や、ストリームのクローズ等、テストの理解の役に立たないものは、@Beforeや@Afterメソッドに移す。
良いテストでは、他の関数を探しまわらなくても処理内容を把握できる。
誤解を招く構成
テスト中でどの部分がAAA(セットアップ、処理、アサーション)に対応しているかがわかりやすくなるようにすると、意図が明確になるため理解しやすくなる。
適切なタイミングで空行を入れる等。
暗黙の意味付け
なぜその結果になるのかが明確になるようにテストを書くこと。
まとめ
テストを通じてシステムを理解してもらうという意識を持ってテストを作成すべき。
第Ⅳ部 より大きなテストの全体像
第12章 テスト駆動開発
TDDの主なメリット
リファクタリングはリスクを伴う作業だが、ユニットテストがあれば積極的に行える。
TDDのサイクル
- 失敗するテストを作成する
- テストが成功するようにコードを記述する
- 1と2で追加あるいは変更されたコードをクリーンアップする。
高コストな誤った仮定を避けるためにも、TDDでの初回のテストは必ず失敗させること。
テストのクリーンアップ
- TDDでは、ほぼすべてのコードについて安全にリファクタリングが可能である。
- TDDを成功に導くには、シナリオをテストに分解し、必要なコードの追加量を最小限にできるような順番で取り組む。
APIの拡張
- TDDを使うと最善の設計を得られるわけではないが、テストがあることとテストを通じてよりよい設計へとリファクタリング可能になる。
ドキュメントとしてのテスト
- テストの名前を見ればテスト対象の振る舞いを理解できるようにすべき。テスト名を明確かつ一貫性のあるものにすれば、よりよい。
- 1つのテスト対象クラスに対して複数のテストクラスを作成してもよい。
定期的にクラス名やテスト名の正しさを確認すること。
TDDの周期性
- 短い時間(なれたら数分程度でできる)でテスト・コード作成、リファクタリングの周期を回す。
- 悪いコードは積極的に捨てる。
第13章 テストが難しい事柄
マルチスレッドのコードのテスト
物事はシンプルに
- スレッドの制御とアプリケーションのコードが重複する部分を減らす。スレッドを使わなくてもコードの大部分をテストできるようにする。そして、残されたスレッド関連の小さな部分についてのテストも作成する。
- 並列処理関連のライブラリを使用する(java.util.concurrentパッケージに含まれる)。BlockingQueueクラス等。
アプリケーションロジックの抜き出し
モック化したメソッドの検証
Mockitoのverifyを使って行う。
// foundMatchメソッドの引数がmatchingProfile, setで呼ばれたことの確認
verify(listener).foundMatch(matchingProfile, set);
// foundMatchメソッドが呼ばれなかったことの確認
verify(listener, never()).foundMatch(noMatchingProfile, set);
この他にもメソッドが呼ばれた回数の検証などもできる。
データベースのテスト
- 永続化に関する処理は隔離すべき。
- 実際にデータベースとのやりとりを行うコードとの統合テストは必須だが、設計と保守が難しいため、ユニットテストで検証できるロジックを増やし、統合テストの数や複雑さを減らすよう心がけるべき。
まとめ
- スレッドやデータベースなどからアプリケーションロジックを切り離すべき。
- モックを使い、遅いコードや変更の多いコードへの依存を減らす。
- 必要なら統合テストを作成してもよいが、シンプルかつ目的を絞ったものにすること。
プロジェクトでのテスト
共通の理解を深める
ユニットテストへの取り組み方は開発者ごとに違うが、チームのメンバーが共通の理解を持つことが重要
ユニットテストの基準を定める
最低限の基準として、以下の2つ
- 開発者にとって、チームの時間を最も消費していると感じているのはどのようなことか
- チーム全員がすぐに合意できるような、シンプルな基準とは何か
レビューを通じて基準への準拠を促進する
- フェイガン検査
- Upsource:IntelliJのコードレビュー機能付きプラグイン 日本語のブログ http://siosio.hatenablog.com/entry/2015/06/27/024415
ペアプログラミングでのレビュー
事後的なレビューでは、レビュアーが対象のコードについて精通していなかったり、また事後的なレビューでは遅すぎる場合などがある。
ペアプログラミングは能動的なコードレビューである。
継続的インテグレーションとの統合
最近の開発チームにとって、継続的インテグレーションサーバーは必須要件である。
カバレッジ
カバレッジは傾向を把握する目的に利用する程度にとどめ、具体的な数字にこだわらないこと。
望ましいカバレッジの値
- カバレッジが低いと、悪いコードが増える
- 100%は達成しなくてよい(捏造しないと難しい)
- 70%以下ではテストが不十分
カバレッジの意義
カバレッジが低いコードや、値が減少傾向にあるチームでのみカバレッジツールは役に立つ