テスト
読書メモ
単体テスト

【読書メモ】経験ゼロでもできるプログラミング現場の単体テスト

More than 1 year has passed since last update.

雑感

  • 古い書籍(初版2009年)だが、単体テストの基礎を学ぶのにちょうどよい分量と内容だと感じた
  • この内容を土台に、別の書籍でさらに知識を増やしていこうと感じた
  • 以下のメモは自分にとって有益になりそうな箇所及び内容に絞っている

単体テストは侮れない

超多忙な現場になぜ単体テストを勧めるのか

  • テスト=システムが仕様どおりに動作するかどうか確認すること

テスト観点

  • 単体テスト
    • プログラム単体での振る舞いが仕様どおりであるか
  • 結合テスト
    • プログラム間のデータの受け渡しができているか
  • システムテスト
    • 要件を満たしているか
  • 負荷テスト
    • システムに大きな負荷がかかっても、問題なく稼働するか
  • セキュリティテスト
    • 脆弱性を突かれてシステムをダウンさせられたり、機密情報を閲覧されたりしないか

Webアプリケーションの単体テストをよく知ろう

単体テストを楽にするには

  • テスト範囲を小さくする
    • 作ったそばからテストする
  • TDDにこだわらない
    • 実装とテストの時間を空けないことが重要
    • 次の実装を意識するのはテストが終わってからにする

十分な単体テストを実施するために

テスト手法

  • ホワイトボックステスト
    • 文や分岐、条件などを実行してバグを見つけるテスト
    • プログラムの内部処理に着目したテスト
カバレッジ 内容
ステートメントカバレッジ(命令網羅) 全ステートメント(命令・文)のうち、1回でもテストで実行されたステートメントの割合。
ブランチカバレッジ(分岐網羅) 全ブランチ(分岐)のうち、1回でもテストで実行された分岐の割合。
分岐のどちらも(true/falseそれぞれ)通る必要がある。
コンディションカバレッジ(条件網羅) 分岐に設定されている1つ1つの条件について、成立したときと不成立のときの両方がテストで実行された割合。
1つの分岐に複数の条件が設定されている場合、各条件ごとに実行の有無を確認する。

ex) 下記のコードの場合のテストケースを考える
```
if (a == 0 && b < 0) {
...
}

// テストケース
1. a = 0, b = 0 :左true、右false
2. a = 1, b = -1:左false、右true

// カバレッジ
ブランチカバレッジ :50%
コンディションカバレッジ:100%
```

  • ブラックボックステスト
    • ある入力に対し、仕様どおりに正しい結果が戻されるかどうか確認するテスト
    • プログラムの内部処理に着目しないテスト
テスト手法 内容
同値分割法 プログラムが期待する入力値=「有効同値」とそれ以外=「無効同値」のそれぞれに代表値を用意し、
それらを入力値としてプログラムを実行した結果を確認する方法。
境界値分割法 有効同値と無効同値の境界となる値を入力値としてプログラムを実行し、その結果を確認する方法。

**
ホワイトボックステスト、ブラックボックステストのどちらも行って十分な単体テストとなる!
それは、テストの観点がプログラムなのか振る舞いなのかので異なるため。
**

行う価値の高い単体テスト

ビジネスロジックと外部リソースアクセスのテスト

テストクラスの作り方

  • 処理の大きなメソッドは処理をメソッド分割し、各メソッドに対しテストケースを起こす

  • 分割した元のメソッドに対しては、各メソッドの戻り値と各メソッドを呼び出した際の引数を比較することで適切な呼び出しがされていることを確認する

  • できれば画面側のロジックから呼び出されるビジネスロジックのインターフェース(引数の型、例外)を先に設計し、ユーティリティやDBアクセスなど、末端のクラスから先に実装すると、ビジネスロジックの単体テストがしやすくなる

Mockオブジェクトの使いどころ

  • Mockオブジェクトを使わないとテストできない状況にするのではなく、Mockオブジェクトを使用せずにテストできる箇所を増やすように実装設計する

  • 依存クラスの作成タスクが自分以外で、それが間に合っていない場合に適している

    • この場合もメソッド分割で対応できないかなど、依存クラスの完成に依存している箇所を最小限にする工夫が必要

外部リソースへアクセスする処理のテスト

  • ファイル情報を入力データとして処理を行う場合のテスト

    • テストクラスの作り方同様、ビジネスロジックをメソッド分割して各テストケースを作成し、ファイル読み込み→各ビジネスロジックの取りまとめ処理での呼び出しテストを行う実装設計にする
  • ファイルにデータを書き込む処理を行う場合のテスト

    • 読み込み処理と同様、ファイルが存在しなくてもテストできる単位でメソッド分割する
    • テスト時の出力先は実ファイルではなくメモリにする
    • 結果比較の際の比較先は、期待する実ファイルをメモリにロードするパターンと期待するバイト配列を用意しておき比較するパターンでテストできる

メール送信を行うメソッドのテスト

  • テストクラスの作り方同様、ビジネスロジックをメソッド分割して各テストケースを作成し、各ビジネスロジックの取りまとめ処理での呼び出しテストを行う実装設計にする

  • メール送信を行う箇所のテストを減らすことで目視確認の負担を軽減する

外部システム連携と画面側のテスト

  • Mockオブジェクトを使う

    • 多用は禁物
    • どうしてもテストできない箇所にのみ使用すべき
    • Mockオブジェクトに定義するのは、渡された引数をチェックすることと、戻り値(あるいは例外)を返すこと
  • Mockオブジェクトの種類

    • 実行する際に、実際のオブジェクトとMockオブジェクトをバイトコードレベルで置き換えるタイプ
    • 振る舞いを記録させたMockオブジェクトを処理対象のインスタンスとして置き換えるタイプ
  • 画面側の単体テスト

    • View、Controllerの単体テストはテスト仕様書を元にWebブラウザから手動で実行するのが現実的
    • 手動でテストを行うにしてもカバレッジ率の確認は必要

継続的にテストをするために

実装前にテストを意識する(設計段階で考えておくべきポイント)

  • 品質を意識するのはプロジェクト内の誰かではなく、プロジェクトメンバー全員でなければならない

よいテストの条件

  • 目的が明確である
    • 結果と期待値の比較
    • 異常終了することなく各メソッドが呼び出せている
  • 繰り返し実行可能である
    • いつ何度実行しても同じ結果になる
    • 前準備や後処理も自動化されている
  • 環境に依存しない
    • どのマシン上でも実行でき、同じ結果になる
  • テストが独立している
    • 他のテストの結果に依存しない
  • 処理の依存関係が疎である
    • テスト対象メソッドを実行する際に、実クラスであろうがMockオブジェクトであろうが依存するクラスが少ないほうがよい
    • テスト対象メソッドが小さければ目的が明確になり、テストのために依存するクラスも少なくなる

テストを行いやすいアーキテクチャを検討する

  • レイヤードアーキテクチャ

実装時にテストを意識する(無駄なテストを減らす設計)

引数からテストをしやすくする

  • 引数の型に意味を持たせる
    • ビジネスロジックはデータ内容とチェックとその処理だけに集中すべき
      • ビジネスロジックの先頭で型変換を行わず、引数の型をあらかじめ指定の型にしておくべき
      • 結果的にテストケースを減らすことに繋がる
  • 引数チェックの指針
    • publicメソッドでは行う
    • DTOやPOCOのsetterで制限を加えてセットしたプロパティを作ることなどもテストケースを減らすよい実装
  • 引数のオブジェクトに対する変更
    • 基本的な単体テスト
      • 戻り値が期待値と合っているか比較する
      • 引数のオブジェクトが呼び出し前から変わらないこと/変わることの検証は行わない
    • 引数がオブジェクト(参照の値渡し)の場合はテスト対象メソッドの呼び出し前後で引数の状態が想定どおりか確認する
      • 基本的には引数のオブジェクトは参照のみに使用する設計にし、変更されていないことを確認するテストケースは省略する
      • 引数のオブジェクトを変更する必要のあるメソッドでは変更されていることを確認する

システム日付を扱うメソッドのテスト

  • デバッグモードを実装し、テストの際にはデバッグモード=ONにして固定値を返すようにする
    • 環境変数などをうまく使うとよい

発見しづらいバグを作らない

  • テストしやすいメソッドは、引数を元に処理を行い、戻り値を返すメソッド
  • 依存クラスの責務に対するチェックはほどほどにしておく
    • 依存クラスに修正が入った場合にテストの修正箇所が増えすぎると保守性が低下する
  • 文字コードの指定ができる箇所は基本的に指定する