Open8

Study | 単体テストの考え方/使い方

TakayyzTakayyz

単体テストでは、1単位のコード(a unit of code)を検証するのではなく、1単位の振る舞い(a unit of behavior)を検証するようにします。
〜略〜
ここで意味する単体は複数のクラスにまたがることもあれば単一のクラスに収まることもあり、さらには、ちょっとしたメソッドの実装で終わることもあります。

2.3.1 TIP

「単体」の指す粒度や認識を統一することは大事。

TakayyzTakayyz

(複雑な依存のあるクラスをテストする際に)本来考えるべきことは膨大で複雑な依存関係を持つクラスを検証するための方法を見つけ出すことではなく、そのような複雑な依存関係を構築しなくても済むようにするための方法
〜略〜
テストの作成において、もし、準備(Arrange)フェーズがあまりにも大きくなるようであれば、何らかの設計の問題がそこにある可能性が高いのです。このような場合、モックを使って複雑な依存関係を隠しても、根本的な問題解決にはなりません。

2.3.2

テスト・ダブルを用いてテスト時に依存について考えなくてもよくするのではなく、そもそも設計を見直すことを検討する。

TakayyzTakayyz

2章まとめ

単体テストの定義

  • 1単位の振る舞い(a unit of behavior)を検証すること
  • 実行時間が短いこと
  • 他のテストケースから隔離された状態で実行されること
    • ロンドン学派:テスト対象となる単体(クラス)を他の単体から隔離すべきという考え方(=テスト・ダブルを多様することになる)
    • 古典学派:単体テストのテストケースをそれぞれ隔離しなくてはならないという考え方

テストが失敗した際に原因の特定が容易なのはロンドン学派であるが、コード変更毎にテストを実行するという前提があれば古典学派(1単位の振る舞いをテストするやり方)でもテストが失敗する直前に変更を加えたコードに問題があることが明確になる為、原因の特定が難しくなることはほとんどない。

TakayyzTakayyz

※前提としてAAAの構造を採用

もし、準備(Arrange)フェーズが他の2つのフェーズを合わせたサイズよりもはるかに大きくなるのであれば、その一部を同じテスト・クラスのプライベートなメソッドに切り出したり、その部分を作成する別のファクトリ・クラスを作ったりしたほうが良いでしょう。このような準備フェーズのコードをテスト・ケース間で共有することに関する有用なパターンとしてよく知られているものの中にオブジェクト・マザー(Object Mother) と呼ばれるパターンとテスト・データ・ビルダー(Test Data Builder) と呼ばれるパターンがあります。

3.1.4

TakayyzTakayyz

確認(Assert)フェーズで確認する項目はどのくらいあればよいのか?

単体テストにおける「単体」とは、1単位のコードではなく1単位の振る舞いのことです。1単位の振る舞いによって複数の結果が生じることはあり得ることであり、ひとつのテスト・ケースでその結果をすべて検証することは自然なことなのです。

3.1.5

基本的に1つの結果しかassertしないようにしていたから考え方が少し変わった。

TakayyzTakayyz

テストの可読性の為に。

  • もし、AAAパターンを採用したテスト・ケースにおいて、準備フェーズや確認フェーズのコードを書くのに空白行が不要なのであれば、各フェーズの頭に付けているコメントを取り除き、空白行で各フェーズを区切るようにする。
  • そうでない場合は、各フェーズの頭にそのフェーズを示すコメントをつけるようにする

3.1.8

test('準備フェーズ・確認フェーズに空白行不要なテスト', function () {
    $hoge = 1;
    $piyo = 'piyo';
    $sut = new Hoge();

    $actual = $sut->handle($hoge, $piyo);

    expect($actual)->toBe('expected');
});

test('準備フェーズ・確認フェーズに空白行必要なテスト', function () {
    // Arrange
    $hoge = 1;
    $hogehoge = 2;
    $piyo = 'piyo';

    $xxx = $hoge + $hogehoge;

    $sut = new Hoge();
    // Act
    $actual = $sut->handle($xxx, $piyo);
    // Assert
    expect($actual)->toBe('expected');
    expect($actual->zzz)->toBeTrue();

    expect($actual->yyy)->toBe('yyy');
});