なんとなく FizzBuzz をテストファーストで書いてみることにした。
====Eclipse 上で JMockit を使ってやってみる。
仕様は、3の倍数なら Fizz、5の倍数なら Buzz、3と5の公倍数なら FizzBuzz、それ以外ならその整数自体を標準出力に書き出すというもの。範囲は 1 から 100 までにしよう。main() から実行する。
まずテストクラスから
public class FizzBuzzTest {
@Test public void main() {
FizzBuzz.main(new String[0]);
}
}
クラス FizzBuzz が存在しないので、エディタ上で赤い下線が引かれている。Ctrl+1 でクラス生成ダイアログを開いて、main() メソッド付きのクラスを自動作成する。
public class FizzBuzz {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
コンパイルが通ったら一応 JUnit を実行してグリーンになるのを確認。
次に、とりあえず一番最初に"1" が表示されることを確認したい。テストコードの方を以下のように書き換える。
public class FizzBuzzTest {
@Mocked("println(String)") PrintStream mock;
@Test public void main() {
new Expectations() {{
mock.println("1");
}};
FizzBuzz.main(new String[0]);
}
}
PrintStream をモックして、System.out.println()の呼び出しを確かめている。
これを実行すると、パラメータ"1"で呼ばれるはずの println() が呼ばれなかったという事で、赤になる。本体コードを以下のように直して、再実行。今度はグリーンになるので、JMockit を含めた疎通確認ができた。
public static void main(String[] args) {
System.out.println("1");
}
さて、与えられた仕様をどうテストするか。まず 100個の数字ってとこからやってみる。実装としては、println() が呼ばれる度に、パラメータを List に溜め込んでおいて、後で調べる方式にしてみよう。こんなコードになる。
@Test public void main() {
final List<String> texts = new ArrayList<>();
new NonStrictExpectations() {{
mock.println(anyString); result = new Delegate<PrintStream>() {
void println(String x) { texts.add(x); }
};
}};
FizzBuzz.main(new String[0]);
assertEquals(100, texts.size());
}
Delegate ってのを使って、PrintStream#println(String) を書き換えて、受け取った文字列をローカルのリスト texts に溜め込むようにした。実行すると赤くなるから、本体コードも以下のように直して、グリーンにしておく。
public static void main(String[] args) {
for (int i = 1; i <= 100; i++)
System.out.println("" + i);
}次に、数字と文字列の対応付けのあたりをやる。以下をチェックする。
| 始点の1 | ・・・ | 1 ⇨ "1" |
| 3とその前後 | ・・・ | 2 ⇨ "2", 3 ⇨ "Fizz", 4 ⇨ "4" |
| 5とその前後 | ・・・ | 4 ⇨ "4", 5 ⇨ "Buzz", 6 ⇨ "Fizz" |
| 15とその前後 | ・・・ | 14 ⇨ "14", 15 ⇨ "FizzBuzz", 4 ⇨ "16" |
| 終点の100 | ・・・ | 100 ⇨ "Buzz" |
@SuppressWarnings("unused")
public class FizzBuzzTest {
@Mocked("println(String)") PrintStream mock;
@Test public void main() {
final List<String> texts = new ArrayList<>();
new NonStrictExpectations() {{
mock.println(anyString); result = new Delegate<PrintStream>() {
void println(String x) { texts.add(x); }
};
}};
FizzBuzz.main(new String[0]);
assertEquals(100, texts.size());
FizzBuzzAssertion a = new FizzBuzzAssertion(texts);
a.assertIt("1", 1);
a.assertIt("2", 2);
a.assertIt("Fizz", 3);
a.assertIt("4", 4);
a.assertIt("Buzz", 5);
a.assertIt("Fizz", 6);
a.assertIt("14", 14);
a.assertIt("FizzBuzz",15);
a.assertIt("16", 16);
a.assertIt("Buzz", 100);
}
static class FizzBuzzAssertion {
final List<String> actuals;
FizzBuzzAssertion(List<String> actuals) { this.actuals = actuals; }
void assertIt(String expected, int number) {
assertEquals(expected, actuals.get(number - 1));
}
}
}
FizzBuzzAssertion クラスはコード重複を避けるために導入した。そうしないと、以下のようなコード重複が生じる。また、リストの数字と文字列の対応付けもずれて読みにくいので、これも解消した。
assertEquals("1", texts.get(0));
assertEquals("2", texts.get(1));
assertEquals("Fizz", texts.get(2));
assertEquals("4", texts.get(3));
テストを実行すると赤になるので、本体コードを以下のように書き換える。
public static void main(String[] args) {
for (int i = 1; i <= 100; i++) {
System.out.println(
0 == i % 15 ? "FizzBuzz":
0 == i % 3 ? "Fizz":
0 == i % 5 ? "Buzz":
Integer.toString(i));
}
}
再実行してグリーンになるのを確認。これでテストコードと本体コードの TDD 終わり。
一応機能テストとして、本体コードの main を実行して、コンソールを目視確認しておく。問題なし。
====
こんな感じで、FizzBuzz 自体は屁だけど、テストファーストでやるとモッキングのテクが、若干必要になってくる。
0 件のコメント:
コメントを投稿