Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

KOMIYA Atsushi @komiya_atsushi 2

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

今⽇日のお話をする動機 • 「あるメソッドから返却されるオブジェ クトの内容が想定しているとおりか?」 といったテストコードを実装しなきゃい けない • イメージ的には  #equals() 呼び出しを 前提とした  assertThat(actual,   is(expected)); による等価⽐比較なんだ けど、検査対象のオブジェクト構造や検 査条件がちょっと複雑なんだよね… 7

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

アプローチ 1.  assertThat()  をひたすら並べる 2.  #equals()  を実装する 3.  専⽤用の  Matcher  を実装する 9

Slide 10

Slide 10 text

1.  assertThat()  をひたすら並べる 10 /**    *  検索索結果を表します  */   public  class  SearchResultDto  {          /**  検索索条件に合致する結果の総数  */          public  Integer  numAllItems;            /**  検索索を開始した開始位置  */          public  Integer  fromIndex;            /**  取得した件数  */          public  Integer  numItemsInPage;            /**  検索索された⽇日時  */          public  Date  searchedAt;            /**  書籍情報  */          public  List  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 さしみたんぽぽ作業過ぎてつらい…

Slide 11

Slide 11 text

2.  #equals()  を実装する 11 /**    *  検索索結果を表します  */   public  class  SearchResultDto  {          /**  検索索条件に合致する結果の総数  */          public  Integer  numAllItems;            /**  検索索を開始した開始位置  */          public  Integer  fromIndex;            /**  取得した件数  */          public  Integer  numItemsInPage;            /**  検索索された⽇日時  */          public  Date  searchedAt;            /**  書籍情報  */          public  List  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  を使う

Slide 12

Slide 12 text

/**    *  検索索結果を表します  */   public  class  SearchResultDto  {          /**  検索索条件に合致する結果の総数  */          public  Integer  numAllItems;            /**  検索索を開始した開始位置  */          public  Integer  fromIndex;            /**  取得した件数  */          public  Integer  numItemsInPage;            /**  検索索された⽇日時  */          public  Date  searchedAt;            /**  書籍情報  */          public  List  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 テストコードはとってもすっきり!

Slide 13

Slide 13 text

3. 専⽤用の  Matcher  を実装する 13 class  SearchResultDtoMatcher  extends  BaseMatcher  {          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;                  }                  ...          }          ...   }  

Slide 14

Slide 14 text

3. 専⽤用の  Matcher  を実装する 14 class  SearchResultDtoMatcher  extends  BaseMatcher  {          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()  の⽐比ではない

Slide 15

Slide 15 text

いろいろ⾒見見てきたけど… • どれもつらいですね  L • テストコードはもっと楽に書きたいよね • ついでに⾔言うと Groovy 界隈だと⼀一般 的っぽい  Power Assert 的なメッセージ も欲しい… 15   と、いうわけで・・・  

Slide 16

Slide 16 text

汎⽤用的な等価⽐比較  Matcher  を実装してみた • github.com/komiya-atsushi/shibuya-java4 • これを使うと、上記してきた例例の検証が これぐらいシンプルに… 16 //  verify   assertThat(actual,          isEquivalentTo(expected)                  .exclude(pathPattern("object.searchedAt")));  

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

IsEquivalentTo の概要&機能(2) • ⽐比較的親切切(?)なメッセージ 19 ま、まあ、ないよりはまし… かな…

Slide 20

Slide 20 text

デモ 20

Slide 21

Slide 21 text

こんなクラスを使います 21 /**    *  ワードカウント機能を提供します。  */   public  class  WordCount  {          /**  各単語の出現頻度度を保持します  */          public  Map  wordCounts;            /**  最頻出する単語の上位3つを保持します  */          public  List  top3Words;            /**  ワードカウントの処理理に要した時間(ミリ秒)を保持します  */          public  long  elapsedMillis;            /**  元の⽂文章を保持します  */          public  Date  text;   }   ここは等価⽐比較の対象外としたい

Slide 22

Slide 22 text

まとめ • オブジェクトの等価⽐比較なテストコード なら、楽に記述できるようになると思う よ! • がんばれば… 22

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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