社内「JUnit実践入門」読書会第二回まとめ #junitbook

前回に引き続き、第2回目の「JUnit実践入門」読書会を開催しました。
社内「JUnit実践入門」読書会第一回まとめ #junitbook - 旧山pの楽しいお勉強生活

今回は参加者は少なかったですが、本格的なJUnitの内容になってきたこともあり充実した読書会になったと思います。

開催日程とか

日時 2/9(土) 10:00〜12:00
場所 自社会議室
参加者 2名

対象範囲

第16章 テスト駆動開発 - テストファーストで設計する -
第4章 アサーション - 値を比較検証する仕組み -
第5章 テストランナー - テスト実行方法の制御 -
第6章 テストのコンテキスト - テストケースの構造化 -
第7章 テストフィクスチャ - テストデータや前提条件のセットアップ -
第8章 パラメータ化テスト - テストケースとテストデータの分離 -
第9章 ルール - テストクラスを拡張する仕組み -
第10章 カテゴリ化テスト - テストケースのグループ化 -

第16章 テスト駆動開発 - テストファーストで設計する -

16.1 テスト駆動開発とは?
  • テストファースト
    • どのようなテストが成功すれば目的のものが得られるか、というゴールを先に明確にする。
    • 短いサイクルを繰り返して開発を進める手法。
    • 1回のサイクルではゴールをなるべく近い場所に設定。
    • 短い距離をゴールまで進むサイクルをリズムよく繰り返し、最終的なゴールを目指す。
16.2 テスト駆動開発のサイクル
  1. 設計する
  2. テストコードを書く(レッド:テスト失敗)
  3. プロダクションコードを書く(グリーン:テスト成功)
  4. リファクタリングする(グリーン:テスト成功)
  5. 次のサイクルを始める
16.3 Calculatorクラスのテスト駆動開発
  • 実例なのでここでは省略
16.4 テスト駆動開発の目的

プログラマの不安を軽減すること。

  • ステップバイステップ
    • 小さなモノを少しずつ安心して構築していく。
  • 自分が最初のユーザ
    • 自分が使用することで使いやすいさの改善。
  • 動作するきれいなコード
    • 完璧な設計をしてから実装するのではなく、動くプロダクションコードを実装した後で最適な設計に修正します。
  • すばやいフィードバック
    • プロダクションコードをコミットした時点でテストコードが既に存在しているため、継続的テストや継続的インテグレーションを導入していると、すばやいフィードバックを最大限に得ることが可能。
  • メンテナンスされたドキュメント
    • テストコードが実際に動くサンプルであり、ドキュメント。
    • コメントやドキュメントはメンテナンスがおざなりになることが多いが、テストの成功を維持することで、メンテナンスされたドキュメントを得ることができる。

第4章 アサーション - 値を比較検証する仕組み -

4.1 Assertによる値の比較検証
  • org.junit.Assertクラスは、JUnitで値の比較検証を行うための基本クラス。
  • 自然言語に近い表記を実現するためにstaticインポートを行なって利用する。
assertThat(3 + 4, is(7));
→ assert that '3 + 4' is 7.
  • assertThat 汎用的な値の比較検証
    • 値の比較検証の殆どをassertThatメソッドで行うことができます。
    • 一つ目の引数には実装値、2つ目の引数には期待値との比較を行うMatcherオブジェクトを指定。
    • isメソッドは、オブジェクト同士をequalsメソッドで比較するMatcherオブジェクトを返す。
    • assertThatでの比較検証で値が一致しない場合、AssertionErrorを送出する。
    • 値が一致した場合には何も行わない。
  • fail テストを失敗させる
    • 無条件にそのテストを失敗させる。
    • テストメソッドを作成したがテストコードを記述していないなどの時に使用。
    • 意図しない成功を防ぐ場合にも。
  • そのほかのアサーションメソッド
    • assertEqualsやassertTrueなどのメソッドもあるが、互換性のために存在してるので、基本的にはassertThatとfailメソッドの2つを使用する。
4.2 Matcher APIによるアサーションの特徴
  • 可読性の高い記述
    • より自然言語に近いテストコードになる。
    • 基本的には↓の形にかける。
assertThat(actual, is(expected));
  • 柔軟な比較
    • 可読性を損ねず、最利用可能で柔軟な比較検証を行える。
    • 下記2つでは下のほうが可読性が高い。(上はcontainsのテストになってしまっている。)
List<String> actual = sut.getList();
assertTrue(actual.contains("Hello"));
List<String> actual = sut.getList();
assertThat(actual, hasItems("Hello"));
  • 定義されているMatcherに適当なモノがなければ、カスタムMatcherを作成することも可能。
    • カスタムMatcherのテストは入念に行う必要があるため、プロジェクト単位などで共通で提供されるべき。
4.3 Matcher APIの使用
  • CoreMatchersが提供するMatcher
    • is
      • equalsメソッドでの比較
        • クラスにequalsが正しく実装されていないと当然正しい結果にはならないので注意。
      • Matcherオブジェクトのファクトリメソッドを組み合わせることで、柔軟な表現が可能。
      • assertThat(actual, is(not(expected)));
    • nullValue
      • nullであることを検証するMatcherを返す。
      • 通常はisと組み合わせて使用
assertThat(actual, is(nullValue()));
    • not
      • 他のMatcherの評価値を反転させる。
      • isと組み合わせることで自然言語に近い形で評価できる
assertThat(actual, is(not(0)));
    • notNullValue
      • nullではないことを比較検証する。
      • ↓と同義。
assertThat(actual, is(not(nullValue())));
      • プロジェクトで統一されていないと嫌。(後で修正とかになったらもっと嫌。)
    • sameInstance
      • 実装値と期待値が同一のインスタンスであるかを比較するMatcherを返す。(つまり「==」を使用して比較する。)
    • instanceOf
      • 実装値が期待するクラスのインスタンスと互換性のある型であるかを比較するMatcherを返す。
      • instatnceof演算子を使用して比較を行う。

  • JUnitMatchersが提供するMatcher
    • hasItem
      • リストなど反復可能なオブジェクトに期待する値が含まれているかを判定するMatcherを返す。
  • hasItems
    • リストなど反復可能なオブジェクトに期待する値が複数含まれているかを判定するMatcherを返す。(可変長引数をとる)
assertThat(actual, hasItems("Hello", "World"));

4.4 カスタムMatcherの作成

プロジェクト固有の検証や、より柔軟な検証を行いたいといった場合にカスタムMatcherを作成する。

  • クラス作成
  • ファクトリメソッドを作成
  • コンストラクタを定義
  • 実際に比較を行うmatchesメソッドの実装
  • matchesがfalseの場合に呼ばれるdescribeToメソッドの実装

定義されているMatcherに適当なモノがなければ、カスタムMatcherを作成することも可能。
→ カスタムMatcherのテストは入念に行う必要があるため、プロジェクト単位などで共通で提供されるべき。
テストしたい項目とequalsでの比較は異なる場合があるので、Matcherがあると便利!

第5章 テストランナー - テスト実行方法の制御 -

5.1 コマンドラインからのJUnitの実行

実際にコマンドラインから実行することなんてほぼない。

5.2 テストランナーとは?

テストランナーとは、テストクラスのテストケースを「どのように実行するか」を制御するしくみ。

5.3 JUnitが提供するテストランナー
  • JUnit4
    • テストクラスの全テストケースメソッドを実行する。

ここでいうテストケースメソッドとは

  1. publicメソッド
  2. @Testが付与されている
  3. 戻り値がvoidで引数を持たない

明示的にテストランナーを指定しなかった場合にはこのJUnit4テストランナーが使用される。

  • Suite
    • 複数のテストクラスをまとめて実行する
    • テストクラスを主導で追加しなければならないので、外部のビルドツールなどで利用するのが現実的。
  • Enclosed
    • 構造化したテストクラスを実行する。
    • ○○の場合などを内部クラスとして定義し、その中にテストケースを記述していく。
    • 共通の初期化処理はsetUpメソッドに抽出する事で、可読性が高いテストコードを作成可能。

→ JenkinsだったかAntだったかEclipseだったか忘れたけど、2重にカウントされる事があった気がする。
Gradleでの記事はあったけど、↑の環境の記事はみつからず。これもまたどこかで見たような。
GradleでEnclosedのテストが二回実行されるんだ - 日々常々

  • Theories
    • パラメータ化したテストケースを実行する。
    • テストケースとテストデータを分離し、
    • 同じテストメソッドを複数のパラメータで再利用して使うテクニック。
      • エラーとなった際にエラーとなった値をメッセージに渡せなくて困った気がする。(ここでコケたという事だけわかれば良いか?)
  • Categories
    • 特定カテゴリのテストクラスをまとめて実行する。
    • テストケースをカテゴリ化し、実行するテストケースをフィルタリングするためのテストランナー。

目印となるカテゴリクラスを作成し、このカテゴリクラスを@Categoryの引数として指定する。

第6章 テストのコンテキスト - テストケースの構造化 -

6.1 テストのコンテキストとは?
  • テストに関連する内部状態や前提条件。
  • テストの事前条件や状態、テストの入力データ、期待値となるデータなど
6.2 テストケースの整理
  • テストクラスの構造化
  • テストケースをグループ化する。

初期化処理を共通化できるようにグループ化する。

  • Enclosedによるテストクラスの構造化
    • ○○の場合などを内部クラスとして定義し、その中にテストケースを記述していく。
    • 共通の初期化処理でクラス分けをして、setUpメソッドに抽出する事で可読性が高いテストコードを作成可能。
    • ネストしたクラスにEnclosedテストランナーを指定することも可能だが、3階層以上にすることは可読性の面から避けるべき。
6.3 コンテキストのパターン
  • 共通のデータに着目する
  • 共通の状態に着目する
  • コンストラクタのテストを分ける
6.4 テストクラスを横断する共通処理
  • 共通処理はユーティリティクラスなどに抽出する。(その際に日本語メソッド名だと可読性が良い)
  • テストクラスからテストとは直接関係しない共通処理を抽出する場合にはルールを使用する。

第7章 テストフィクスチャ - テストデータや前提条件のセットアップ -

7.1 テストフィクスチャとは?

テストで扱うデータやテスト実行環境、オブジェクトの状態の事。

  • ユニットテストのフィクスチャ
    • テスト対象オブジェクト
    • テストの実行に必要なオブジェクト(入力値)
    • テストの検証に必要なオブジェクト(期待値)
    • テストの実行までに必要なテストオブジェクトの操作
    • ファイルなどの外部リソース
    • データベースやソケットサーバなどの外部システム
    • 依存クラスや依存外部システムのモックオブジェクト
  • フレッシュフィクスチャ
    • フィクスチャはテストケースごとに独立し、テスト実行ごとに初期化され、終了時に開放する。
  • フィクスチャとスローテスト問題
    • スローテスト問題とはデータベースの初期化などセットアップに長い時間がかかってしまう問題のこと。
  • 共通フィクスチャ
    • 各テストケースで使用するフィクスチャを共有し、再利用することでセットアップコストを抑える。
    • テストケースの独立性が弱くなり問題点が多いため、可能な限りフィクスチャは共有しない。
    • テストケースの実行順序による影響。
    • メンテナンス性の低下
7.2 フィクスチャのセットアップパターン
  • インラインセットアップ
    • その名の通り1行づつ通常通りセットアップする。
    • 長所 : テストコード内で完結し、見通しが良い。
    • 短所 : 複雑で長い場合に可読性が悪い
  • 暗黙的セットアップ
    • @Beforeによってテストケース実行前に実行されるセットアップ。
  • 生成メソッドでのセットアップ
    • 独立したクラスにstaticメソッドとして作成。
    • セットアップ内容が一目でわかるように日本語メソッド名で作成する。
      • staticインポートできるからprivateよりいいって書いてあるけど、クラス内だったらそもそもクラス名いらないような。。。
  • 外部リソースからのセットアップ

yaml、使っている人は4人居た。でも便利…?
読み易さは段違いに良い。ただ若干方言的な部分もあるのでそこは気になる。

http://d.hatena.ne.jp/absj31/20130112/1358154059

第8章 パラメータ化テスト - テストケースとテストデータの分離 -

8.1 テストデータの選択
  • どのくらいのテストデータが必要か
    • 同値クラスによるテストデータの選択
    • 組み合わせによるテストデータの選択
  • どのテストデータを選択するか
    • 境界値を使用する
8.2 入力値の期待値のパラメータ化

@DataPointを使用すると組み合わせがわかりずらい。
@DataPointsを入力値、期待値を設定したクラスの配列に設定するのが一番わかり易い。

8.3 組み合わせテスト
  • Assumeによるパラメータのフィルタリング
    • assumeThatメソッドは引数に実測値とMatcherオブジェクトを受け取り、値の比較検証を行う。
    • assertThatと異なり、検証結果が偽の場合にはテストの失敗とならず、
    • そのテストを無視してテストの実行を継続する。

使用したことがないが、うまく使えるのか?
結局assumeの設定を誤っていたらテストが成功になってしまう。使う場合がよくわからない。
横浜の読書会でも↓の感じだったらしい。

この章に関してはそもそもの『パラメータ化テストの使い方』をどう上手く扱うか、assumeとの兼ね合い等について議論がなされていましたね。
それぞれ機能としては提供されているものの、意図する加減で使いこなしているかというと...という感じでした。

http://d.hatena.ne.jp/absj31/20130112/1358154059
8.4 パラメータ化テストの問題
  • データの網羅性
    • フレームワークで検知できる問題ではない。
    • テスト技法を学び、適切なデータを使用する。
  • パラメータに関する情報の欠落
    • どのパラメータで実行時に失敗したのかの情報が出力されない。(何番目のパラメータかは出力される)
    • assertThatの第1引数にメッセージを指定することでメッセージの出力は可能。

第9章 ルール - テストクラスを拡張する仕組み -

9.1 ルールとは?

ユニットテストを拡張できる機能
共通で使う処理を独立したクラスに定義可能。

  • ルールのしくみ
    • セットアップメソッドなどと同様に、テストメソッドの実行ごとに行われる。
  • 複数のルールの宣言
    • 複数ルールの宣言も可能だが、実行順序の制御は不可。
9.2 JUnitが提供するルール
  • TemporaryFolder テンポラリフォルダの作成と開放を行うルール
  • ExternalResource 外部リソースを扱うカスタムルールを定義するための基底クラス
  • Verifier テスト後の事後条件を検証するルール
  • ErrorCollector テスト時の例外の扱いをカスタマイズするルール
  • ExpectedException 詳細な例外に関する検証を行うルール
  • Timeout テスト時のタイムアウトを制御するルール
  • TestWatcher テスト実行時の記録を行うルール
  • TestName 実行中のテストメソッドのメソッド名を参照できるルール

→ 読み込みリソースの名前をメソッド名にしとくと動的に読み込めていいかも!(要確認

9.3 カスタムルールの作成
  • 簡単にカスタムルールの作成が可能。
  • TestRuleインターフェイスを実装。(それ以外にも既に実装済みのExternalResourceクラスを継承したクラスを作成するなど。)
9.4 RuleChainによるルールの連鎖
  • 通常、複数のRuleを宣言した場合には順番は制御されないが、RuleChainを使用することで順番制御が可能。
9.5 ClassRuleによるテストクラス単位でのルールの適用

横浜の読書会では↓のような感じ。

機能そのものについては『ふむ、なるほど』と疑問点は特に挙がってこなかったですが、『どういう局面で使うだろうか』という辺りに話が及んでました。ExpectedExceptionは使いそうだけどその他はどうだろう…みたいな。

http://d.hatena.ne.jp/absj31/20130112/1358154059

第10章 カテゴリ化テスト - テストケースのグループ化 -

10.1 スローテスト問題
  • スローテスト問題とは
    • 遅いテストが発生していること
  • 対策
    • テストの実行時間を短くする
    • テストの実行環境を強化する
    • テストを並列で実行する
    • 実行するテストを絞り込む
10.2 カテゴリ化テスト
  • カテゴリ化テストとは
    • アノテーションでタグ付けされたテストケースから特定のタグを含む(または含まない)テストケースのみを実行するしくみ
  • カテゴリ化テストの実行
10.3 カテゴリ化テストのパターン
  • データベーステスト
  • 通信処理を伴うテスト
  • GUIを伴うテスト

横浜の読書会では↓のような感じ

問題ある、時間食うようなものは別途分けるべき。
Googleでは厳格に取り決めをしているらしい。
個人で取り組むよりも、チーム等で取り決めて行うことだよね...

http://d.hatena.ne.jp/absj31/20130112/1358154059
10.4 ビルドツールによるカテゴリ化テスト
  • 設定ファイルなどに対象カテゴリを指定することで、カテゴリ化テストを実行可能。