B-Teck!

お仕事からゲームまで幅広く

【本】JUnit実践入門 体系的に学ぶユニットテストの技法を読んだ

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

本書は、全15章のトピックを5つのPartに分けて解説している。
Partごとに自分が重要だと思ったトピックを抜き出して読書メモとしてまとめた。

Part 1 JUnit入門

ユニットテストとは?

ユニットテストは、自分の書いたコードに責任を持ち、変更に強いプログラムを書く手法である。
あるプログラムが想定通りの振る舞いをするかテストするプログラムを書くことで、
その仕様を明確にし、変更や改善を継続的に行っていける仕組みを作ることができる。
想定する振る舞いがプログラムとして記述されているので、テストコードはそれ自体が仕様書のようなものになる。

ユニットテストに求められるパターン

実行順や状態に依存せず、単体で繰り返し実行できるテスト
振る舞いをわかりやすく記述し、問題発生時にはその特定が容易になるようなテスト

良いテストコードとは?

前提条件、入力値とその操作、期待値が必要十分であること
一つのケースで複数のテストを行っていないこと

テストしやすいコード

戻り値を持ち、副作用がなく、同じ入力に対して常に同じ結果を返す関数的メソッドがよりテストしやすい
ユニットテストを意識することでテストしやすいコードを書くことができる

JUnit4の代表的なアノテーション

  • @Test:テストコードの宣言(expectedで例外のテスト、timeoutで実行時間のタイムアウトを設定するなどできる)
  • @Ignore/実装中などものをテストの実行から除外する
  • @Before/テストクラス内の各メソッドを実行する前に行う初期化処理
  • @After/テストクラス内の各メソッドを実行後に行う終了処理
  • @BeforeClass/テストクラス内の最初のテストメソッドを実行する前に行う初期化処理
  • @AfterClass/テストクラス内の最後のテストメソッドを実行後に行う初期化処理

ユニットテストの基本パターン

  • 標準的な振る舞いのテスト(4フェーズテスト)
    • 事前準備/オブジェクトの初期化や前提条件の設定
    • 実行/処理の実行
    • 検証/処理結果の確認
    • 後処理/場合によっては次のテストに影響が出ないような処理が必要
  • 例外の送出テスト
  • コンストラクタの生成テスト

Part 2 第2部 JUnitの機能と拡張

Matcher

JUnitではMatcherという仕組みでテスト結果の検証を行っている。
標準で実装されている代表的なMatcherメソッドは下記

  • CoreMatchers

    • is/期待値と結果をequalsで比較する
    • nullValue/nullであることを検証する
    • not/引数のMatcherの結果を反転させる
    • notNullValue/nullでないことを検証する
    • sameInstance/期待値と結果が同じインスタンスであることを検証する
    • instanceOf/結果が期待値するクラスのインスタンスであることを検証する
  • JUnitMatchers

    • hasItem/リストや配列などに期待値が含まれている
    • hasItems/リストや配列などに複数の期待値が含まれている

その他にも、自分でカスタムしたMatcherを定義することができる

テストランナー

JUnitでは、テスト実行対象のカスタマイズのために、テストランナーという仕組みを用意している。
標準では下記が用意されている

  • JUnit4/テストクラスの中の有効な全テストケースを実行する
  • Suite/複数のテストクラスをまとめて、有効な全テストケースを実行する
  • Enclosed/同じ初期化を行うテストケース等をまとめて構造化したテストクラスを実行する
  • Theories/@DataPointと組み合わせて、テストメソッドとパラメータを分離したパラメータ化テストを実行する
  • Categories/テストクラスをまとめてカテゴリ化し、特定のカテゴリのテストを実行する

テストのコンテキスト

テストケースが増え、テストクラスが大きくなってくると可読性が下がるので、構造化による分割を行う必要がある。
テストクラスの構造化にはテストランナーのEnclosedが便利。
同じコンテキスト(テストに関連する前提条件や状態、操作やデータ等)の単位で分割しまとめる。

  • 同じメソッドをテストする単位
  • 同じ前提条件や初期化、後処理を行う単位

テストフィクスチャ

4フェーズテストの事前準備に該当する部分。テストの前提条件となる操作や状態をフィクスチャと呼ぶ。
フィクスチャをテストケースごとに独立させ、都度生成するフレッシュフィクスチャという考え方が基本。

ユニットテストのフィクスチャは主に下記のような要素

  • テスト対象オブジェクト
  • 実行に必要なオブジェクト(入力値)
  • 検証に必要なオブジェクト(期待値)
  • テストに必要なオブジェクト操作
  • テストで扱う外部リソース
  • テストで扱う外部システム
  • モックオブジェクト

フィクスチャのセットアップを行うパターンは下記

  • インラインセットアップ/テストメソッド内でフィクスチャのセットアップ
  • 暗黙的セットアップ/@Beforeで抽出した初期化処理を用いたセットアップ
  • 生成メソッドでのセットアップ/生成メソッドを作成し、セットアップ済みのフィクスチャを生成させる
  • 外部リソースからのセットアップ/外部リソースに内容を定義し、読み込んでセットアップ

パラメータ化テスト

入力値と期待値をテストメソッドから分離して定義し、パラメータとして利用する手法。
@Theories、@DataPoint(s)を利用して組み合わせを網羅したテストを実施する。
対象外のパラメータの組み合わせについては、Assumeクラスを用いて除外することができる

ルール

ユニットテストの拡張機能を適用する仕組み。テストケース実行ごとに適用・実行される。
JUnit標準で用意されるルールの他、TestRuleインターフェースの実装をすることでカスタムルールを作成できる。

標準で用意されているルール

  • TemporaryFolder/テスト実行時に一時フォルダを作成し、終了時に破棄する
  • ExternalResource/テスト実行時に外部リソースを準備し、終了時に破棄する
  • Verifire/テスト終了後の状態を検証する
  • ErrorCollector/エラーが発生してもテストを継続し、全てのテストを実行する
  • ExpectedException/例外オブジェクトに対して詳細なテストが必要な場合にその詳細を扱うことができる。
  • Timeout/テストのタイムアウトを設定する
  • TestWatcher/テスト実行を監視し、実行中に処理をインタラプトさせることができる
  • TestName/実行中のテスト名称を取得する

クラスルール

@ClassRuleを用いるとクラス単位でのRuleの適用を行うことができる。
BeforeClassやAfterClassで行ったような操作をルールとして記述する仕組み。

カテゴリ化テスト

スローテスト問題

ユニットテストを実施していくと、次第にテストの実行速度が遅くなってくる。
ユニットテストのサイクルが遅くなることで、有効ではなくなってしまうことがある。
その原因は主に下記の3つ

  • データベーステスト
  • 通信処理を伴うテスト
  • GUI操作を伴うテスト

その対策は主に下記の4つ

  • 最適化により実行時間を短くする
  • 実行環境の強化
  • テストの並列実行
  • テスト対象の絞込み

テスト対象の絞込みを行う仕組みのカテゴリ化テスト

テストクラスをカテゴリに分割し、目的に応じたテストを任意のタイミングで行う手法。
スローテスト問題を起こしやすい箇所をカテゴリ分けすることで普段は除外してテストを行ったりできる。

Part 3 ユニットテストの活用と実践

テスタビリティ

  • テスタビリティの高いテスト対象とは

    • 複雑な状態を持たない
    • 多くの引数を持たない
    • 単一の目的を処理している
  • テスタビリティを高めるにはリファクタリング

    • メソッドの抽出
    • 移譲オブジェクトの抽出

テストダブル

テスト対象の依存するクラスを置き換える代役オブジェクト。
依存するオブジェクトの振る舞いを固定したり、呼び出しを検証する際に用いる。

よく利用されるのは下記の三種類

  • スタブ
    依存するクラスやモジュールの代用となるオブジェクト
    不安定なテストケースを検証可能な形とすることを目的にしている
  • モック
    依存するクラスやモジュールの代用となるオブジェクト
    依存先モジュール等を正しく呼び出しているかの検証を目的にしている
  • スパイ
    依存オブジェクトの呼び出しを記録・監視するために、オリジナルのオブジェクトをラップしたオブジェクト
    副作用のあるテストケースなどで結果を確認するために用いる

カバレッジ

カバレッジとは、ユニットテスト実行時のプロダクションコードの実行割合。
実装済みのロジックを検証するものであり、仕様自体を保証するものではない。
常時測定し、一定の割合を維持することで品質を保つ指標とできる。

コードカバレッジの基準は下記(ただしC2はほぼ用いられない)

  • C0(命令網羅)/命令が実行された割合
  • C1(分岐網羅)/分岐が実行された割合
  • C2(条件網羅)/真偽条件の組み合わせが実行された割合

Part 4 開発プロセスの改善

継続的テストとは?

バージョン管理システムの変更等をトリガーに自動的にテストを実行する手法。
早期から自動で繰り返しテストされるため、フィードバックからの改善を行いやすい。
継続的テストを行うには、バージョン管理、自動化、ユニットテストの導入が必要。
Maven等のビルドツールを用いる場合や、Jenkins等のCIツールを用いる場合もある。

テスト駆動開発(TDD)/振舞駆動開発(BDD)

テスト駆動開発とは?

テストファーストで開発することを原則とした開発手法。
先にテストを書くことで仕様を明確にしながら実装を進めることができる。
テストを意識した設計になるので見通しがよくなりやすい。

振舞駆動開発とは?

ソフトウェアがどのように振る舞うかをシナリオとして定義してテストを行う手法。
TDDよりも大きい粒度で、外部から見た機能や期待される仕様を定義する。

Part 5 演習問題(演習問題のため割愛)

感想

良かった点悪かった点それぞれあるものの、総合的に読んで良かった本だった。
悪かった点も発売時期を考えれば仕方のないといったところで、内容についてはほぼ不満はない。
少し古い情報の本なので手放しには勧められないものの、ユニットテストについて知りたいという人には勧められる本。

  • 良かった点
    JUnitの使い方の解説にとどまらず、一般的なテストの技法や用語の解説、
    なぜこの機能が使われるのかという解説を含んでいるので、初学者でも一通りの知識を得ることができる。
    実践入門の名前にふさわしく、ユニットテスト導入時に最初に読む一冊として必要な内容を網羅している印象。

  • 悪かった点 この本が書かれたのが2012年のため、扱われているプラグイン等は現在は更新が止まっているものや、
    もう使われていないものなども含まれている点が難点。
    最新の事情に合わせると、実践は自分で調べながらの部分が多少出てくることになると思う。