SE情報技術研究会’s blog

http://se-info-tech.connpass.com

2015-12-27 『JUnit実践入門』読書会(第4章)の振り返り

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

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

第4章 アサーション

  • Assert#assertThatメソッドとMatcher APIを使って値の比較検証を行う。
  • staticインポートを利用することを想定している

assertThatメソッド

  • 値の比較検証のほとんどを行う
  • 第1引数=実測値
  • 第2引数=期待値との比較を行うMatcherオブジェクト
  • 比較検証で値が一致しない場合、AssertionErrorが発生する
コメント
  • assertThatかJUnit3までのassertXXXのほうが好みかは人によって分かれるもよう
    → assertThatだと途中でisとか入れたくない
    → 旧メソッドの期待値を第1引数に入れるのが好きではない
    などなど

failメソッド

  • 無条件にテストを失敗させるメソッド
  • テストを実装していないことを明らかにしたい場合に使われる
  • あるブロックが実行されないことをテストしたい場合に使われる

Matcher API

  • 何かの値に対して等価かを柔軟に比較するためのライブラリ
  • ユニットテストのほとんどは等価かの比較
  • 要素を含むか(hasItems)などの比較方法も提供し、一貫した書式でテストの意図を伝えやすい
    assertTrue(actual.contains(expected))だとactualにexpectedを含むテストなのか、containsメソッドの意味なのかわかりにくい
    → hasItemメソッドは内部でcontains使ってる?
    → forループを使ってる

IsCollectionContaining.java

  protected boolean matchesSafely(
      Iterable<? super T> collection,
      Description mismatchDescription) {
    boolean isPastFirst = false;
    for (Object item : collection) {
      if (this.elementMatcher.matches(item)) {
        return true;
      }
      if (isPastFirst) {
        mismatchDescription.appendText(", ");
      }
      this.elementMatcher.describeMismatch(item,
          mismatchDescription);
      isPastFirst = true;
    }
    return false;
コメント
  • assertEqualsメソッドだと、どのフィールドが一致しないかの詳細な情報が伝わらない?
    → カスタムのMatcher APIを使わなくてもassertEqualsメソッドだけでも実装次第でできるのでメリットを感じられない?
    → 単にassertEqualsメソッドでもフィールド単位で較すればいいだけでは?
  • オブジェクトにequalsメソッドをきちんと実装してテストを行うのは問題?
    → あとでフィールドが追加された際にテスト実装するときにオブジェクトのequalsメソッド修正時に実装漏れに気づく?
    → フィールドが追加された場合に、実装がプロダクションでもテストでも抜けてしまっった場合、どっちにしろテストは失敗しないので気づかれないのでは?

CoreMathers

is

nullValue

assertThat(astual, is(nullValue()));

not

assertThat(actual, is(not(0)));

notNullValue

assertThat(actual, is(notNullValue()));

これは下記と同じ

assertThat(actual, is(not(nullValue())));

sameInstance

  • 値の比較ではなく同じインスタンスかの比較
  • equalsではなく==の比較
  • 基本データ型はボクシング変換されるので使うべきではない

instanceOf

  • 実測値(actual)が比較するクラスと同じクラスもしくは比較クラスを継承したクラスならtrue
  • 実測値(actual)がnulの場合は失敗になる

JUnitMathers

hasItem

  • 実測値に期待値が含まれているか判定する
  • 実測値は配列やIterableのオブジェクト

hasItems

  • 実測値に複数の期待値が全て含まれているか判定する
  • 複数の期待値を設定することが可能
  • 期待値の全てが含まれないとtrueにはならない

カスタムMatcherの作成

日付の比較を行うカスタムMathcerの作成

次の要件を満たすカスタムMatcherを作る

  • 年月日をそれおぞれintで指定して比較できる
  • 時分秒は無視する
  • 失敗したらyyyy/mm/dd表記で確認できるようにする

手順

  1. 独自Matcherクラスの生成

  2. org.hamcrest.baseMathcerを継承する

  3. matchesメソッドとdescribeToメソッドを実装する
  4. matchesメソッドは比較検証を行う
  5. describeToメソッドは失敗時の情報を作成する

  6. ファクトリメソッドを作成する

  7. Matcherを使いやすくするためstaticなファクトリメソッドを用意する

  8. 期待値を受け取るコンストラクタを用意し、ファクトリメソッドから生成する

  9. Matchesメソッドを実装する

  10. boolean値で結果を返すmatchesメソッドを実装する

  11. describeToメソッドを実装する

  12. matchesメソッドがfalseの際に呼び出される

  13. 引数のDescriptionインスタンスに情報を追加する
  14. appendText:文字列を追加するメソッド
  15. appendValue:値を追加するメソッド。出力時に「"」で囲まれる
  16. 本のフォーマットと違い4.12だと実測値がbut: wasになっている
  17. Descriptionに追加した情報はExpected isに表示される

テスト失敗時のメッセージ

Expected is "2000/01/01" but actual is "2015/12/27"
  but was <Sun Dec …略>
  • 期待値にエラー情報含めて出すのに抵抗ある
    メソッドが実績値、期待値含めたメッセージがつくれない点が微妙
    → 期待値にエラー情報含めて出すのはメッセージとして破たんしている
  • そもそも標準で用意されているメッセージ自体も実績値と期待値のインデントをそろえて出力したい(長文の文字列比較などやりやすいように)