Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JUnit のオブジェクト等価比較を怠けたい! #渋谷java

E77287648aff5484ac7659748e45c936?s=47 KOMIYA Atsushi
November 16, 2013

JUnit のオブジェクト等価比較を怠けたい! #渋谷java

第4回 渋谷java http://connpass.com/event/3744/ でお話した「JUnit のオブジェクト等価比較を怠けたい!」の発表資料です。発表で使用するはずだったデモコードは https://github.com/komiya-atsushi/shibuya-java4 こちらのリポジトリにあります。ブログエントリは http://blog.k11i.biz/2013/11/4-java-junit-matcher.html こちら。

E77287648aff5484ac7659748e45c936?s=128

KOMIYA Atsushi

November 16, 2013
Tweet

Transcript

  1. JUnit の オブジェクト等価 ⽐比較を怠けたい! 渋⾕谷Java #4 2013.11.16 at BizReach KOMIYA

    Atsushi ( @komiya_atsushi )
  2. KOMIYA Atsushi @komiya_atsushi 2

  3. 分析⼒力力をコアとする マーケティングソリューションカンパニー だいたい何でもござれな よろずやエンジニアやってます 3

  4. 分析⼒力力をコアとする マーケティングソリューションカンパニー だいたい何でもござれな よろずやエンジニアやってます 4 代々⽊木の緑⾊色の会社

  5. #TokyoWebmining 事務局 分析/機械学習/アドテク等のネタで お話していただける講師を募集中デス! 5

  6. by Sebastian Bergmann http://www.flickr.com/photos/sebastian_bergmann/8127016855/ えっと、すみません 前回に引き続き 今回も  JUnit  の お話です…

    6
  7. 今⽇日のお話をする動機 • 「あるメソッドから返却されるオブジェ クトの内容が想定しているとおりか?」 といったテストコードを実装しなきゃい けない • イメージ的には  #equals() 呼び出しを 前提とした  assertThat(actual,

      is(expected)); による等価⽐比較なんだ けど、検査対象のオブジェクト構造や検 査条件がちょっと複雑なんだよね… 7
  8. 例例 8 /**    *  アイテムを検索索します  */   public  SearchResultDto

     search(String...  conditions)  {  ...  } /**    *  検索索結果を表します  */   public  class  SearchResultDto  {          /**  検索索条件に合致する結果の総数  */          public  Integer  numAllItems;            /**  検索索を開始した開始位置  */          public  Integer  fromIndex;            /**  取得した件数  */          public  Integer  numItemsInPage;            /**  検索索された⽇日時  */          public  Date  searchedAt;            /**  書籍情報  */          public  List<BookDto>  items;   } /**    *  書籍の情報を表します。  */   public  class  BookDto  {          /**  アイテムの識識別⼦子  */          public  Long  id;            /**  種類  */          public  Integer  type;            /**  書籍名  */          public  String  title;            /**  このオブジェクトが作成された⽇日時  */          public  Date  createdAt;            /**  詳細情報  */          public  List<DetailDto>  details;   }   ああ、#equals() が実装 されてないんだ  (´・ω・`) メソッド呼び出し時刻に 依存した値になるのね… (検証では対象外としたい)
  9. アプローチ 1.  assertThat()  をひたすら並べる 2.  #equals()  を実装する 3.  専⽤用の  Matcher

     を実装する 9
  10. 1.  assertThat()  をひたすら並べる 10 /**    *  検索索結果を表します  */  

    public  class  SearchResultDto  {          /**  検索索条件に合致する結果の総数  */          public  Integer  numAllItems;            /**  検索索を開始した開始位置  */          public  Integer  fromIndex;            /**  取得した件数  */          public  Integer  numItemsInPage;            /**  検索索された⽇日時  */          public  Date  searchedAt;            /**  書籍情報  */          public  List<BookDto>  items;   } assertThat(actual.numAllItems,  is(100));   assertThat(actual.fromIndex,  is(20));   assertThat(actual.numItemsInPage,  is(10));     //  searchedAt  はあえて⽐比較しない     assertThat(actual.items,  hasSize(2));   assertThat(actual.items.get(0).id,  is(123));   ...   L さしみたんぽぽ作業過ぎてつらい…
  11. 2.  #equals()  を実装する 11 /**    *  検索索結果を表します  */  

    public  class  SearchResultDto  {          /**  検索索条件に合致する結果の総数  */          public  Integer  numAllItems;            /**  検索索を開始した開始位置  */          public  Integer  fromIndex;            /**  取得した件数  */          public  Integer  numItemsInPage;            /**  検索索された⽇日時  */          public  Date  searchedAt;            /**  書籍情報  */          public  List<BookDto>  items;   } public  boolean  equals(Object  o)  {          if  (!(o  instanceof  SearchResultDto))  {                  return  false;          }            SearchResultDto  other  =  (SearchResultDto)o;          if  (!this.numAllItems.equals(other.numAllItems))  {                  return  false;          }          ...   }   public  boolean  equals(Object  o)  {          if  (!(o  instanceof  SearchResultDto))  {                  return  false;          }            SearchResultDto  other  =  (SearchResultDto)o;          return  new  EqualsBuilder()                  .append(this.numAllItems,  other.numAllItems)                  ...                  .isEquals();   }   普通に実装する commons-lang  を使う
  12. /**    *  検索索結果を表します  */   public  class  SearchResultDto  {

             /**  検索索条件に合致する結果の総数  */          public  Integer  numAllItems;            /**  検索索を開始した開始位置  */          public  Integer  fromIndex;            /**  取得した件数  */          public  Integer  numItemsInPage;            /**  検索索された⽇日時  */          public  Date  searchedAt;            /**  書籍情報  */          public  List<BookDto>  items;   } public  boolean  equals(Object  o)  {          if  (!(o  instanceof  SearchResultDto))  {                  return  false;          }            SearchResultDto  other  =  (SearchResultDto)o;          if  (!this.numAllItems.equals(other.numAllItems))  {                  return  false;          }          ...   }   public  boolean  equals(Object  o)  {          if  (!(o  instanceof  SearchResultDto))  {                  return  false;          }            SearchResultDto  other  =  (SearchResultDto)o;          return  new  EqualsBuilder()                  .append(this.numAllItems,  other.numAllItems)                  ...                  .isEquals();   }   普通に実装する commons-lang  を使う 12 2.  #equals()  を実装する //  verify   assertThat(actual,  is(expected));   L でも  #equals()… J テストコードはとってもすっきり!
  13. 3. 専⽤用の  Matcher  を実装する 13 class  SearchResultDtoMatcher  extends  BaseMatcher<SearchResultDto>  {

             private  SearchResultDto  expected;          private  String  message;            @Override          public  boolean  matches(Object  item)  {                  SearchResultDto  actual  =  (SearchResultDto)  item;                                    if  (!actual.numAllItems.equals(expected.numAllItems))  {                          message  =  "numAllItems  の値が異異なります";                          return  false;                  }                  ...          }          ...   }  
  14. 3. 専⽤用の  Matcher  を実装する 14 class  SearchResultDtoMatcher  extends  BaseMatcher<SearchResultDto>  {

             private  SearchResultDto  expected;          private  String  message;            @Override          public  boolean  matches(Object  item)  {                  SearchResultDto  actual  =  (SearchResultDto)  item;                                    if  (!actual.numAllItems.equals(expected.numAllItems))  {                          message  =  "numAllItems  の値が異異なります";                          return  false;                  }                  ...          }          ...   }   J fail  したときに値が⼀一致 しない箇所を明確にできる L コード量量は  #equals()  の⽐比ではない
  15. いろいろ⾒見見てきたけど… • どれもつらいですね  L • テストコードはもっと楽に書きたいよね • ついでに⾔言うと Groovy 界隈だと⼀一般 的っぽい  Power Assert

    的なメッセージ も欲しい… 15   と、いうわけで・・・  
  16. 汎⽤用的な等価⽐比較  Matcher  を実装してみた • github.com/komiya-atsushi/shibuya-java4 • これを使うと、上記してきた例例の検証が これぐらいシンプルに… 16 //  verify  

    assertThat(actual,          isEquivalentTo(expected)                  .exclude(pathPattern("object.searchedAt")));  
  17. IsEquivalentTo の概要&機能(1) • 概要 • イメージ的には、commons-lang  の   EqualsBuilder#reflectionEquals() の考えを   Matcher

     実装に応⽤用したと思えば  OK • 機能 • List / Map / 配列列 •  データ構造を再帰的にたどります •  保持しているオブジェクトが値的に等しいかを検証します • オブジェクト •  Object#equals()  をオーバーライドしていない場合に限り、 プロパティ  / public フィールドの内容を検証します(List / Map  などなら再帰的に!) • 等価⽐比較したくないプロパティ / フィールド •  Json-path  的な記法で指定できます 17
  18. IsEquivalentTo の概要&機能(1) • 概要 • イメージ的には、commons-lang  の   EqualsBuilder#reflectionEquals() の考えを   Matcher

     実装に応⽤用したと思えば  OK • 機能 • List / Map / 配列列 •  データ構造を再帰的にたどります •  保持しているオブジェクトが値的に等しいかを検証します • オブジェクト •  Object#equals()  をオーバーライドしていない場合に限り、 プロパティ  / public フィールドの内容を検証します(List / Map  などなら再帰的に!) • 等価⽐比較したくないプロパティ / フィールド •  Json-path  的な記法で指定できます 18 すべてを説明していると時間がなくなる ので、詳細はコードを御覧ください><
  19. IsEquivalentTo の概要&機能(2) • ⽐比較的親切切(?)なメッセージ 19 ま、まあ、ないよりはまし… かな…

  20. デモ 20

  21. こんなクラスを使います 21 /**    *  ワードカウント機能を提供します。  */   public  class

     WordCount  {          /**  各単語の出現頻度度を保持します  */          public  Map<String,  Integer>  wordCounts;            /**  最頻出する単語の上位3つを保持します  */          public  List<String>  top3Words;            /**  ワードカウントの処理理に要した時間(ミリ秒)を保持します  */          public  long  elapsedMillis;            /**  元の⽂文章を保持します  */          public  Date  text;   }   ここは等価⽐比較の対象外としたい
  22. まとめ • オブジェクトの等価⽐比較なテストコード なら、楽に記述できるようになると思う よ! • がんばれば… 22

  23. ありがとう ございました! 23

  24. ジャバジャバしてる エンジニア募集中デス! 24 分析⼒力力をコアとする マーケティングソリューションカンパニー