Loading...

Chapter27. Generated Value

和田 卓人 (a.k.a id:t-wada or @t_wada)

Press key to advance.

Slides controls, press:

  • and to move around.
  • Ctrl/Command and + or - to zoom in and out if slides don’t fit.
  • T to change the theme.
  • H to toggle syntax highlight.

Generated Value

  • Q. テストに使う値ってどうやって指定すれば良いだろう?
  • A. 毎回生成すればいいんじゃね?
BigDecimal uniqueCustomerNumber = getUniqueNumber();

Generated Value(2)

  • test fixture の作成時に面倒くさいのは、コンストラクタなどにフィールドの値を渡さなければならないこと。そのうちのいくつかはテストの結果に影響があるが、影響の無いフィールドも多い。でも大事なのは、各々違う値を使うこと。そしてテストに重要でない値なら、テストの中からは見えなくしてしまうことがとても重要である。
  • Generated ValueCreation Method と組み合わせて、テストからこのようなノイズを取り除くために使われる。

How It Works

  • テストにどのような値を使うかをテストコードのコーディング時に決めるのではなく、テストの実行時にテストに使う値を生成してしまう。
  • 特定の条件、例えば「データベースで一意であること」なども、テストが進むにつれて実行時に判断し、生成できる。

When to Use It

  • Generated Value の使いどころは、テストに使う値の決定をテスト実行時まで決定できない、もしくはテスト実行時まで遅延したいときである。
  • その値がテストの結果自体には関係なく、かつ Literal Value を書かなければならないことから開放されたいとき
  • その値の満たすべき条件が、テスト実行時にしか判断できないとき
  • 特に SUT が一意の値を要求するとき、 Generated ValueUnrepeatable TestTest Run War が発生することを防ぎ、別のテストが並行で実行されていても事故を起こしにくくなる
  • 更にテストに使う値を一つ一つ別の生成した値にすることにより、テストに使われるオブジェクトの識別を容易にする

When to Use It(2)

  • 注意すべき点としては、異なる値を使うことは異なるバグを引き出す事
  • 一桁の数値はきちんと整形できるが、複数桁の値は整形できないなど
  • Nondeterministic Test を招いてしまう
  • 非決定的な振る舞いが発生したときには SUT を調べ、値の違いが振る舞いの原因なのかを調べなければならない

When to Use It(3)

  • 一般論として Generated Value は、値が一意性を必要としないときには使うべきではない。生成された値によって非決定性がもたらされてしまうからである
  • そういうときは Liretal Value を使うべき
  • 特にテスト結果を予期しなければならないときには、 Derived Value でもよい。

Motivating Example

The following test uses Literal Values for the arguments to a constructor:

public void testProductPrice_HCV() {
  //Setup 
  Product product =
    new Product( 88, //ID
                 "Widget", //Name
                 new BigDecimal("19.99")); //Price
  //Exercise 
  // ... 
} 

Distinct Generated Value

  • 全ての値が異なることを保証したい場合には Distinct Generated Value が使える
  • 様々な型の一意な値を生成するユーティリティを作成する
  • Integer のシーケンスやデータベースのシーケンスが使える
  • 1 からインクリメントさせるタイプの in-memory sequence でも十分で、デバッグもシンプルになる

Distinct Generated Value(2)

public void testProductPrice_DVG() {
  //Setup
  Product product =
    new Product(getUniqueInt(), //ID
                getUniqueString("Widget"), //Name
                getUniqueBigDecimal()); //Price
  //Exercise
  // ...
}

static int counter = 0;

int getUniqueInt() {
  counter++;
  return counter;
}

BigDecimal getUniqueBigDecimal() {
  return new BigDecimal(getUniqueInt());
}

String getUniqueString(String baseName) {
  return baseName.concat(String.valueOf( getUniqueInt()));
}

Random Generated Value

  • テストカバレッジを挙げるための一つの手段に、テスト毎に異なる値を使う、というものがある
  • Random Generated Value をこの手段に使う
  • 良いアイデアに見えるが、テストに非決定性をもたらすので (Nondeterministic Tests) デバッグが難しくなる。
  • 理想的には、テストが失敗した際には、その値でテストを再実行したい。
  • その場合には Random Generated Value をログに取っておいて、その値をデバッグ時に再利用できるような仕組みを作る
  • しかし大体の場合、こういう仕組みを作る努力はそれほど報われない。必要な時は必要なのだが。

Related Generated Value

  • オブジェクト毎に Generated Value を使い、それを Derived Value に組み合わせて使うという手がある
  • 特定の文字列を生成された数値とつなげる等
  • Related Generated Value を使うと一つのオブジェクトの別々のフィールドが互いに関連する値を持つので、把握やデバッグが容易になる
  • generateNewUniqueRoot を明示的に呼び出して、値の root を決めるのも良いね
  • 役割を示す文字列を Generated Value につなげるのはより意図を示すのでよい

Related Generated Value(2)

  • 値生成の root を各々の値組み立てと分離することで、値が互いに関連することを保証することができる
  • この例では値の生成を setUp に移動することによって、必ず1回だけ値が生成されることを実現している
  • 各々の値を生成するメソッドは、生成された root の値を使うだけ
  • これでデバッグ時に値が読みやすくなる

Related Generated Value(3)

public void testProductPrice_DRVG() { 
  //Setup 
  Product product = 
    new Product( getUniqueInt(), //ID 
                 getUniqueString("Widget"), //Name 
                 getUniqueBigDecimal()); //Price 
  //Exercise 
  // ... 
} 

static int counter = 0; 

public void setUp() { 
  counter++; 
} 

int getUniqueInt() { 
  return counter; 
} 

String getUniqueString(String baseName) { 
  return baseName.concat(String.valueOf( getUniqueInt())); 
} 

BigDecimal getUniqueBigDecimal() { 
  return new BigDecimal(getUniqueInt()); 
} 

おわり