📔

「単体テストの考え方/使い方」のアウトプット ~Part3~

2023/08/20に公開

本記事について

Vladimir Khorikov著作の「単体テストの考え方/使い方」を読むにあたり、内容を咀嚼しながらアウトプットすることを目的とする。

引用

https://amzn.asia/d/3BwaVR2

前回

https://zenn.dev/syun43/articles/92646f63e6f49a

第二章 単体テストとは何か?

単体テストの定義には多くのニュアンスが含まれている。
単体テストをどのように行うべきか、ということに関して異なる見解を持った二つの学派が生まれた。

この二つの学派は古典学派ロンドン学派と呼ばれている。

2.1 単体テストの定義

単体テストとして定義されるテストには次の三つの重要な性質がある。

  • 「単体(unit)」と呼ばれる少量のコードを検証する
  • 実行時間が短い
  • 隔離された状態で実行される

この「隔離」の考え方の違いで古典学派とロンドン学派は別れることになった。

2.1.1 ロンドン学派が考える隔離

ロンドン学派の隔離とは、テスト対象システムから協力者オブジェクトを隔離すること。
つまり、テスト対象となるクラスが他のクラスに依存しているのであれば、その依存を全てテストダブルに置き換えるという考え方。

テストダブルとは、リリース対象のオブジェクトと同じような見た目と振る舞いを持ったテストを行いやすくするオブジェクトのこと。

利点

  • テストが失敗した時に、どこで問題が起こったか明確になる

テスト対象システムの依存は全てテストダブルに置き換えられているので、それらの依存は問題を起こした対象から外れる。

  • オブジェクトグラフ(同じ問題を解決するために結びついたオブジェクトの集まり)を分離できる

テスト対象システムと直接的に繋がっている依存がさらに別の依存を持つようになり、依存関係が構築される場合がある。
テストダブル無しでコードをテストするには、本番と同じ複雑なオブジェクトの構成を構築しなくてはいけない。

また副次的な効果として、「一つのテストケースは一つのクラスしか検証しない」という方針をプロジェクト全体に普及できる。
→ テストスイート全体がシンプルな構造になる

2.1.2 古典学派が考える隔離

古典学派では、単体テストにおいて隔離する必要があるのはコードではなくテストケースであり、各テストケースはお互いに影響を与えることなく個別に実行できるようにしなくてはならない、という考え。

テストケース同士が隔離状態であるため、他のテストケースで状態が共有されているとテストケース同士がコミュニケーションを取れてしまう。
→ 共有される状態となるものはデータベースやファイルシステム等がある(プロセス外依存)

三つの依存について

- 共有依存
テストケース間で共有される依存のこと。
依存を扱う複数のテストケースが同時に実行されてしまうと、お互いの検証に影響を与えて正しい結果を得られなくなる。

- プライベート依存
共有されない依存のこと。

- プロセス外依存
アプリケーションを実行するプロセス外で稼働する依存のこと。
異なるプロセス間でデータを受け渡しをする際によく使われ、まだメモリ上にないデータを仲介する役割を担っている。

古典学派の単体テストでテストダブルが使われるのは、テストケース間で共有される状態を持つ依存に対してのみ。

※ 共有依存とは単体テストのテストケース間で共有される依存のことであり、テスト対象となるクラス(単体)間での共有ではない。

また、共有依存をテストダブルに置き換える理由として「テストの実行速度を上げるため」もある。
→ データベースやファイルシステムなどの共有依存への呼び出しは、プライベート依存への呼び出し時間と比べて時間がかかるため

2.2 古典学派およびロンドン学派が考える単体テスト

隔離に対する考え方の違い

  • ロンドン学派:単体テストにおいてテスト対象システムをその協力者オブジェクトから隔離しなくてはならない
  • 古典学派:単体テストのテストケースを別のテストケースから隔離しなければならない
隔離対象 単体の意味 テストダブルの置き換え対象
ロンドン学派 単体 一つのクラス 不変依存を除く全ての依存
古典学派 テストケース 一つのクラス、もしくは同じ目的を達成するためのクラスの一グループ 共有依存

2.2.1 依存の扱い方における古典学派とロンドン学派の違い

ロンドン学派はテストダブルをほぼ全ての依存に対して使うことを推奨しているが、依存の種類によってはテストダブルに置き換えない場合がある。
→ テストダブルを使うかどうかの判断は「可変かどうか」によって決まる
→ 依存が「不変」であれば、その依存はテストダブルに置き換えない

不変なオブジェクトは「値オブジェクト」と呼ばれ、以下の特徴を持つ。

  • 個別の識別性を持たない
  • 自身の内容によってのみ識別される

この不変オブジェクトが二つあった場合、それらが同じ型で同じ内容を持っていれば、それらのオブジェクトは同じものとして扱うことができる。

全てのプロセス外依存が共有依存になる訳ではない。
→ プロセス外依存が共有依存になるためには、その依存を介して他のテストケースがお互いにコミュニケーションをとっていなくてはならない

2.3 単体テストにおける古典学派とロンドン学派の違い

古典学派とロンドン学派の最大の違いは「隔離の扱い」。

筆者は古典学派のスタイルを好む。
なぜなら、古典学派の方が良質な単体テストを作成でき、単体テストの究極な目標である「プロジェクトの持続的な成長を促す」ということを達成するのに向いているから。
→ ロンドン学派の単体テストは古典学派の単体テストよりも壊れやすい

ロンドン学派の長所

  • より細かな粒度で検証ができる

一つのテストケースで一つのクラスしか検証しないため、検証の粒度が細かくなる。

  • 依存関係が複雑になっていても簡単にテストすることができる

全ての協力者オブジェクトがテストダブルに置き換わるため、テストケースを書く際に依存について深く考えなくても良い。

  • テストが失敗した際、どの機能に問題があったか正確に見つけられるようになる

全ての協力者オブジェクトがテストダブルに置き換えられているため、失敗した原因を調べるためにテスト対象システムだけを見れば良い。

2.3.1 細かな粒度による検証

ロンドン学派では、一つの単体が意味することは一つのクラスのことと捉えている。
→ この見解はオブジェクト指向プログラミングの考えから来ている

単体テストでは、一単位のコードを検証するのではなく、一単位の振る舞いを検証するようにする。
→ つまり、問題領域において意味があるもの(ビジネスサイドの人たちが有用であると考える何か)を検証する。

コードの粒度を細かくしようとすることは単体テストにおいて有用ではなく、一単位の振る舞いが検証されていれば良い単体テストとなる。

単体テストにおいて各テストケースがすべきことは、そのテストに関わる全ての人たちにテスト対象のコードが解決しようとしている物語を伝えること

その物語を伝えるためには凝集度を高めて非開発者も理解できるようにすることが必要。

凝集度:ソフトウェアのモジュールやクラスの中の機能や責務が、どれだけ関連しているかの尺度。

2.3.2 複雑な依存関係を持つものに対する単体テスト

協力者オブジェクトをモックに置き換えることはテスト対象となるクラスの検証を簡単にする。
また、複雑な依存関係を断ち切れるので単体テストをする際に必要となる準備の量を減らす。

複雑な依存関係を持つテスト対象システムを古典学派のスタイルでテストするのであれば、複雑な依存関係を作り上げなければいけないので手間がかかる。
→ しかし、この主張は間違った問題に目を向けている

複雑な依存関係が必要になってしまうということは間違った設計が行われていることが分かる。
→ 単体テストはプロダクションコードの欠点を見つけ出すことを得意としているので、単体テストの作成において、設計に問題があることを見抜ける

2.3.3 テストが失敗した際、どこに問題があったかが正確に分かる

ロンドン学派の場合、テストケースが失敗することがあれば、原因はテスト対象のクラスの中に存在することになる。

古典学派の場合、テスト対象システムが呼び出している依存に不具合がある時も、そのテストケースは失敗する。

不具合の原因を見つけ出すことに多くの時間と労力がかかるが、筆者はこのことを問題と考えていない。

理由は、単体テストを頻繁に実施していれば、最後に修正した箇所にテストを失敗させた原因があることが分かるため。

失敗がテストスイートの至る所に広がることで得られるメリットがある。
もし、一つの問題が一つのテストケースではなく多くのテストケースに影響を与えるのであれば、その問題のあったコードは多くのクラスに依存されている重要なコードであることが分かるから。

2.3.4 その他の古典学派とロンドン学派の違い

その他の古典学派とロンドン学派の違いは以下である。

  • テスト駆動開発を用いたシステム設計
  • テストコードが把握することになるプロダクションコードの詳細

簡単にまとめると、

  • ロンドン学派による単体テストのスタイル

外側から内側に向かうテスト駆動開発
→ システム全体がどのように機能するかを考えた広い視野でのテストケースを作成することから始める開発

  • 古典学派による単体テストのスタイル

内側から外側に向かうテスト駆動開発
→ 最初はドメインモデルから実装とテストを始めていき、その後その上の階層の実装とテストの追加を繰り返し行っていく

単体テストはプロダクションコードのことをどのくらい把握しなくてはいけないか?
→ 一般的に、ロンドン学派の方が実装の詳細に深く結びつく場合がある

2.4 古典学派およびロンドン学派における統合テスト

古典学派とロンドン学派の間では統合テストの定義も異なる。

  • ロンドン学派の場合

ロンドン学派では、協力者オブジェクトを使って行うテストを全て統合テストと見なす。
そのため、古典学派のスタイルで書かれたほとんどの単体テストはロンドン学派の視点だと統合テストに分類される。

  • 古典学派の場合

次の性質をもつ自動化されたテストが単体テストとなる。

  • 「単体」と呼ばれる少量のコードを検証すること
  • 実行時間が短いこと
  • 隔離された状態で実行されること

これを古典学派の観点で再定義すると

  • 一単位の振る舞いを検証すること
  • 実行時間が短いこと
  • 他のテストケースから隔離された状態で実行されること

古典学派の統合テストは、上記のどれか一つでも損なっているテストのこと。

テストケースが統合テストに属することになるもう一つのケースは、一つのテストケースの中で一単位の振る舞いを複数検証する場合。
→ 特にテストにかかる時間を短くしようとした場合に起きる

また、統合テストは異なるチームによって開発された複数のモジュールを統合した後、その統合したものが意図したように機能するのかを検証する場合にも使われる。

2.4.1 統合テストの一種としてのE2Eテスト

E2Eテストは一種の統合テストしてみられることがある。

E2Eテストと統合テストの違いは?
→ E2Eテストの方がプロセス外依存を多く含むようになる

統合テストは一個か二個のプロセス外依存を扱うのに対し、E2Eテストは全ての(もしくは、ほぼ全ての)プロセス外依存を扱う。

Discussion