logo

202324

リファクタリングしやすいテストを書こう:単体テストの考え方/使い方 第2部前半

『単体テストの考え方/使い方』(Vladimir Khorikov 著、須田智之訳)を読んでいるので、そのまとめを部ごとに書いていこうと思います。

  1. 単体テストの目的・定義・学派・命名について:単体テストの考え方/使い方 第1部
  2. リファクタリングしやすいテストを書こう:単体テストの考え方/使い方 第2部前半(この記事)
  3. ビジネス・ロジックと連携の指揮を分離すれば良いテストが書ける:単体テストの考え方/使い方 第2部後半
  4. プロセス外依存は統合テストで確認しよう:単体テストの考え方/使い方 第3部

今回は第2部「単体テストとその価値」の前半についての感想と考察になります。第2部は以下の4章で構成されていますが、今回は前半の第4章と第5章について扱います。

  • 第4章:良い単体テストを構成する4本の柱
  • 第5章:モックの利用とテストの壊れやすさ

2023-02-18 更新:記事のスタイルを修正しました。

リファクタリングへの耐性を持つことが常に必要

良い単体テストを構成するものとして、次の4本の柱があります:

  • 退行(regression)に対する保護
  • リファクタリングへの耐性
  • 迅速なフィードバック
  • 保守のしやすさ

『単体テストの考え方/使い方』(Vladimir Khorikov 著、須田智之訳)p.96 より引用。

今回読んだ範囲では「良い単体テストを構成する4本の柱」について説明されていましたが、その中でも一貫して重要視されていたのが2本目の柱リファクタリングへの耐性です。リファクタリングへの耐性は常に最大限にしつつ、同時に成り立たない退行に対する保護迅速なフィードバックについては、テストの種類ごとにどちらを優先するか決めることが推奨されています(p.125)。

fig 4.12

リファクタリングへの耐性を持たせるためのコツは「観測可能な振る舞い」のみをテストし、それを得るまでの過程は見ない(how ではなく what をみる)ということです(pp.127-128)。そのため E2E テストは退行に対する保護を最大限に備えていると言えます。私が所属するチームでも E2E テストのことを普段「リグレッション・テスト」と呼んでおり、その通りだなと思いました。

一方で E2E テストには実行時間が長くかかってしまうので、E2E テストだけで開発を行うのは厳しいです。しかしながら、著者は第5章で「システム内コミュニケーションは実装の詳細になります」と言い、観測可能な振る舞いではない(テストで見るべき最終的な結果ではない)としています。

  • アプリケーション・サービスに対するテスト・ケースはビジネスにおける全体的なユースケースがいかに実行されるのかを検証するのに対し、ドメイン・クラスに対するテスト・ケースは同じユース・ケースが完了に至るまでの一部を検証することになる
    • fig 5.10
  • システム内コミュニケーションとシステム間コミュニケーション
    • システム内コミュニケーションは実装の詳細になります。その理由は、クライアントからのリクエストを処理する際、ドメイン・クラス間で行われるやり取りは観測可能な振る舞いの一部にはならないからです
    • システム間コミュニケーションの場合は違います。なぜなら、テスト対象のアプリケーションがどのように外部とのコミュニケーションを取るのか、ということはそのシステムの観測可能な振る舞い全体を形成するものだからです
  • 外部から観察できないプロセス外依存とのコミュニケーションは実装の詳細になる
    • もし、データベースに対してテスト対象のアプリケーションを除くすべてのアプリケーションからアクセスされることが決してないのであれば、テスト対象のアプリケーションとデータベースのコミュニケーションに関する仕様を(既存の機能を破綻させない限り)好きなように変えられることになります

『単体テストの考え方/使い方』(Vladimir Khorikov 著、須田智之訳)pp.153-164より引用し箇条書きにまとめた。太字は筆者によるもの。図は筆者作成。

そうなるとほとんどやっていることは E2E テストと同じなので単体テストの範疇ではない気がします。これは明らかな矛盾ですが、ここについては第6・7章で説明してくれるようなので続きを楽しみにしたいと思います。

また、単体テストがカバーする範囲を広くしすぎると p-r をレビューする時に、対象のコードが問題ないかの判断をするのが難しくなるのではないかな?という疑問点が浮かんできました。テスト粒度のバランスについても以降の章で説明があるのを期待しています。

モックとスタブ

  • モック:モック、スパイ
    • テスト対象システムからその依存に向かって行われる外部に向かうコミュニケーション(出力)を模倣し、そして、検証する
    • スパイはモックと同じ役割を担う
      • モックはモック・フレームワークの助けを借りて生成される
      • スパイは開発者自身の手で実装される
  • スタブ:スタブ、ダミー、フェイク
    • 依存からテスト対象システムに向かって行われる内部に向かうコミュニケーション(入力)を模倣
    • ダミーとは、null値や一時しのぎで使われる文字列などのシンプルなハード・コーディングされた値のこと
    • スタブはもっと洗練されており、設定によって返す結果を異なるシナリオごとに変えられる完全に自立した依存として振る舞うもの
    • フェイクと使う目的はスタブの場合とほぼ同じである一方、通常、フェイクはまだ存在しない依存を置き換えるために作成される

『単体テストの考え方/使い方』(Vladimir Khorikov 著、須田智之訳)pp.132-134 より引用し箇条書きにまとめた。太字は筆者によるもの。

モックに関連して5つの用語が出てきました。モック・スパイ・スタブ・ダミー・フェイクです。正直、これらの役割のものは全部モックだと思っていました。言葉の意味を知ったからには意識して使い分けていこうと思います。

ここで重要だと思ったのは「スタブとのやりとりを決して検証してはならない」という点です。

リファクタリングへの耐性の話でも出てきたように、観測可能な振る舞いのみをテストするべきということと一貫して、テスト対象システムが持ってくる値を検証することは、テスト対象システムの実装の詳細(how)を調べることになり、過剰検証となります。

今まで実装してきた単体テストを思い返してみて、これをやってしまっていることはあまりないとは思いますが、うっかりするとやってしまいそうなので意識していきたいです。

まとめ

以上、第2部「単体テストとその価値」の前半についてのまとめと所感を拙文ながら書きました。気づきが多いので、自分が携わるコードでもこの本で得た知識を導入しようと試みています。早く全部読み切らなきゃ。ではでは。