なんとなく 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 件のコメント:
コメントを投稿