ラベル UnitTest の投稿を表示しています。 すべての投稿を表示
ラベル UnitTest の投稿を表示しています。 すべての投稿を表示

2012年8月7日火曜日

FizzBuzz をテストファーストで書いてみよう

なんとなく 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 自体は屁だけど、テストファーストでやるとモッキングのテクが、若干必要になってくる。

2012年8月6日月曜日

システム例外を扱うテストコードを JMockit で書いてみる

外部のライブラリで発生した例外のハンドリングを、どうやってテストファーストで書くか。これを示してみる。

====

お題は、以下のようなもの。

java.io の API を使って、テキストファイルから最初の 1行目を読み込むメソッド loadText() があり、クラス ExceptionalFugafuga で定義されている。またテストコードも既に書いてある。ただし IOException 発生時のコードは未定のままになっている。

本体コード。
public class ExceptionalFugafuga {
   public String loadText() {
      try (BufferedReader reader = new BufferedReader(new FileReader("fuga.txt"))) {
         return reader.readLine();
      } catch (IOException exep) {
         throw new AssertionError(); // ★ 後回しの暫定コード
      } 
  }
}
こっちはテストコード。
public class ExceptionalFugafugaTest {
   @Mocked BufferedReader br = null;
   @Mocked FileReader reader= null;
   @Test public void loadText() throws Exception {
      new NonStrictExpectations() {{
         br.readLine(); result="the first line";
      }};
      assertEquals("the first line", new ExceptionalFugafuga().loadText());
   }
}
ここで、IOException をキャッチしたときの暫定コードを、"例外: "+例外メッセージという文字列を返すように修正したい。これをテストファーストでやるとどうなるか。

まずテストコードから。
こんなテストメソッドを追加する。

@Test public void loadText_IOException() throws Exception {
  new Expectations() {{
    new FileReader("fuga.txt"); result = new IOException("はあこりゃこりゃ");
  }};
  assertEquals("例外: はあこりゃこりゃ", new ExceptionalFugafuga().loadText());
}
本体コードがまだそのままなので、テストが失敗して赤くなる。

で、AssertionError() を上げているところを "return "例外: " + exep.getMessage();"に変える。

再度実行し、今度はグリーンになる事を確認する。以上。

====

昔なら、テストメソッド loadText() の @Before とか @After で、実ファイル "fuga.txt" を作ったり消したりしていたところだけど、見ての通り、モックツールを使うとそういうのはいらなくなる。

例外に関しても同じようなことで、例外を発生させる状況を作りだすのではなく、単に例外を投げ上げる振る舞いに差し替えればいい。「Unit」としてテストするとは、本来はこうやって環境から隔離して実施するのものではなかったかと思う。同じやり方は DB例外 でも WebService 例外でも、なんでも応用が効く。

2012年8月5日日曜日

現在時刻に依存する振る舞いのテスト・コーディング

現在時刻が 17時台から 22時台までなら夕方(evening)とする」。

そんなメソッドを、Eclipse と JMockit を使ったテストファーストで書いてみる(こういった実行時に依存する条件を含むコードは、昔はテスト・コーディングが難しかったけど、ツールが進歩した最近はそうでもない)。

====

対象メソッドが CurrentTimeHogehoge クラスの isEvening() メソッドだとすると、テストクラスはこんな感じになるはず

public class CurrentTimeHogehogeTest {
  @Test public void isEvening() {
    assertTrue(CurrentTimeHogehoge.isEvening());
  }
}
この時点では CurrentTimeHogehoge クラスがまだ存在しないから、Eclipse のエディタ上で赤い下線が引かれている。その行で Ctrl+1 を押してクラス生成を選ぶとダイアログが開くから、ソースフォルダを適当に直して Finish。

エディタに戻ると、今度は isEvening() に赤い下線が移っているので、これも Ctrl+1 で 生成する(いちいちタイピングしない)。

コンパイルが通ったところで、こんなコードになっているはず。

public class CurrentTimeHogehoge {
  public static boolean isEvening() {
    // TODO Auto-generated method stub
    return false;
  }
}
試しにテスト実行してみると失敗して赤くなるので、"return false" を "return true" に書き換えて、一旦グリーンにしておく。

続いて isEvening() の実装方針だけど、現在時刻を Calendar の get で取るって事で当たりを付ける。そうすると、テストコードは以下のような感じになる。

public class CurrentTimeHogehogeTest
  @Mocked("get") final Calendar cal = null;
  @Test public void isEvening() {
    new Expectations() {{
      Calendar mock = Calendar.getInstance(); 
      mock.get(Calendar.HOUR_OF_DAY); result = 17;
    }};
    assertTrue(CurrentTimeHogehoge.isEvening());
  }
}
cal を宣言する一行は、意味的にはフィールドの宣言と言うより、Calendar クラスの get メソッドを差し替えるという宣言になる。差し替える振る舞いは Expectations の中に記述していて、ここでは固定値17を返している。

ここでテスト実行してみて、になることをひとまず確認。呼ばれるはずのメソッド、Calendar#get() が 呼ばれなかったと、トレースされているはず。

本体コードの isEvening() は以下のように書き換える。とりあえず時間範囲の下側だけ判別するようにしている。

  public static boolean isEvening() {
    Calendar cal = Calendar.getInstance();
    int hour = cal.get(Calendar.HOUR_OF_DAY);
    return 17 <= hour;
  }
実行するとテストがグリーンになる。

続いて、境界値である、16→偽, 17→真, 22→真, 23→偽 についてテスト・コーディングするわけだけど、コードが重複するのが明らかなので、あらかじめ重複部分を以下のようなメソッドとして切り出しておく。

  private void validateIsEvening (final int hour, final boolean result) {
    new Expectations() {{
      Calendar mock = Calendar.getInstance(); 
      mock.get(Calendar.HOUR_OF_DAY); result = hour;
    }};
    assertEquals(result, CurrentTimeHogehoge.isEvening());
  }
これを、パラメータを変えてテストメソッドから呼び出す。以下のようになる。
@Test public void isEvening() {
    validateIsEvening(16, false);
    validateIsEvening(17, true);
    validateIsEvening(22, true);
    validateIsEvening(23, false);
  }

実行するとテストがになるから、isEvening の最終行を "return 17 <= hour && hour <= 22"に直す。これでテストが成功する。

====

演習だから、テストコードと本体コードの切り替えがかなり小刻みだけど、実戦だったらもうちょいざっくりした感じになると思う

JMockit で Hello World をテストファーストしてみよう

main() メソッドから System.out.println("Hello, World!") してるだけのコードをテストファーストで書くとどうなるか?

これを Eclipse と JMockit でやってみる。

====

まず、こんな感じで普通の JUnitコードを書く。

public class HelloWorldTest {
   @Test public void test() {
      HelloWorld.main(new String [0]);
   }
}
この時点では、まだ HelloWorld クラスがないので、Eclipse のエディタ上では、HelloWorld の下に赤い下線が引かれている。そこにカーソルを移動して Ctrl+1 を押下してクラスの生成を選ぶとダイアログが開くので、main 生成にチェックをいれて Finish。こんなクラスが生成される。
public class HelloWorld {
   /**
    * @param args
    */
   public static void main(String[] args) {
      // TODO Auto-generated method stub
   }
}
Eclipse での作業中はショートカットを活用すると無駄なタイピングをかなり削減できる。

コンパイルが通ったところで、一応、テスト実行してグリーンになることを確認しておく。


次に System.out の println メソッドに "Hello, World!"が渡される事を確認する。

これは JMockit を使うので、pom.xml に以下のように追記する。ただし、インストゥルメンテーションの都合上、<dependencies>の中で junit の前に書かれていなくてはならない。

  <dependency>
   <groupId>com.googlecode.jmockit</groupId>
   <artifactId>jmockit</artifactId>
   <version>0.999.15</version>
  </dependency>

テストコードには以下のように追記する。

public class HelloWorldTest {
   @Mocked PrintStream mock;
   @Test public void test() throws Exception {
      new Expectations() {{
         mock.println("Hello, World!");
      }};
      HelloWorld.main(new String[0]);
   }
}

実行してみるとテストが失敗し、以下のような結果がトレースされる。

mockit.internal.MissingInvocation: Missing invocation of:
java.io.PrintStream#println(String x)
with arguments: "Hello, World!"
on mock instance: java.io.PrintStream@15ebf57
・・・
呼ばれるはずのメソッドが呼ばれていないと、JMockit に指摘されている。

ここで main を以下のように書き換えて、再度テスト実行してみる。

   public static void main(String[] args) {
      System.out.println("test");
   }
失敗して、こんなメッセージがトレースされるが、さっきとメッセージが変わっている。
mockit.internal.UnexpectedInvocation: Parameter "x" of java.io.PrintStream#println(String x) expected "Hello, World!", got "test"
・・・
今度は、"Hello, World!" を期待していたのに"test"が渡されたと言っている。

というわけで、おもむろに"test"を"Hello, World!"に書き換えて、テスト再実行。今度はグリーンになる。

実際のテスト・コーディングよりも、敢えてちょっと回りくどい感じでやってみた。

2012年1月5日木曜日

s/UT/UnitTest/

このブログで今まで UnitTest について書いた投稿には、「UT」というラベルを付けていたが、これを今日、「UnitTest」に付け替えた。

最初は何の気なしに「UT」としてしまっていたけど、最近は、なんだか見れば見るほどウォーターフォール文化における単体テストを表す略号に見えてきて、本来語りたい事とのズレが大きく感じられるようになってきた。

ちなみに本文では、だいたい UnitTest とパスカルケースで書いてきたつもり。これは、ウォータフォールの単体テストと区別するためであると同時に、本家 wiki の仕様に合わせてリスペクトを表明しているからでもあったりする。

ところで、ウォーターフォール色が強い組織・現場では、未だに「実装フェーズ」の直後に「単体テストフェーズ」を設けて、そこで初めて JUnitコード を書き始めるような残念な開発が、特に反省されることもなく日々実践されている。

そういう現場でも、理論的には後付けテストはダメなんだと言う事が、何かの拍子に認知され始めて、一応名目上は「実装・UTフェーズ」みたいに、前後関係をボヤかした感じになったりするのだけど、いかんせんプログラマの意識が「テストの事なんか後で考える」のままだから、実質的には何も変わらないし進歩もない。

泳ぎの真似を陸上でどれだけやったところで泳げるようになんかならないように、後付けテストなんか何年やってもテスト・ファーストで書けるようには全然ならない。

だからそろそろ、現場の開発チームで指導的な立場にある人は、「できればなるべく差し支えない範囲でTDD推奨」みたいな感じではなく、そろそろ「TDD必須」って宣言すべきだと思う。

本当に、一度できるようになったら、というか、できるようになってみないと「テストを先に書く」方がその逆よりも、工数、品質、仕事の面白さなど、どれをとっても格段に優れているという認識は成立しないのだろうと、つくづく思う。

とはいっても、「やってみればテストファーストの方が速い事が分かるよ」なんてやんわりと言って聞かせたところで、どうせ一向に浸透しないのはもう分かった。つう事は、山本五十六も「やってみせ、いって聞かせて、させてみて、 褒めてやらねば人は動かじ」なんて言っているとおり、まずは横に座って実演するところから始めなければならない事になるらしい(やっぱ結局XPに戻ってくる事になるのか…)。

高跳びでも、背中を下に向けた方がその逆より高く跳べるという事に、フォスベリーが記録を出すまでは、だれも気づかなかった。だから、まず「やってみせ」から始めないとダメなのだろう。やってみせる事なしに「言って聞かせて」も結局効果がないし、「させてみせ」も、結局ちょっと目を離したスキにうやむやになってしまう。

・・・つう事を考えているのだけど、本当に残念で仕方が無いが、今の現場では直接コードを書いて実演できるような立ち位置ではなかったりする。次の現場こそは、まずは絶対に、自分で「やってみせ」るところから始められる、実装メインのロールでプロジェクトに参画したい。

2011年11月5日土曜日

FEST+JavaScript で半自動 Swing テスト

あ〜、暇だから、Swing フォームを JavaScript で動かすやり方でも考えてみっかな。

====

例えば、打鍵+目視に基づいた手動画面テストが既に実施されていたり、チェックリストとかテスト仕様書が出来ている場合に、作業負荷を減らすために、後から部分的に自動化を導入して、半自動テストにしたい場合がある。

ところでfest という、Swing アプリを Javaコードから機能テストするための API セットがあり、Javaコード からUIイベントを発生させて画面上のコンポーネントを操作するAPIが一揃い入っている(AWT の Robot をベースにしたものらしい)。

また、JavaScript コードを Javaプログラムから動かす仕組みは、Java 1.6 から備わっていて、特に何か特別なライブラリを必要とせずに、簡単に使えるようになっている。

これらを利用すれば、テスト対象フォームの外で JavaScript を実行して、フォーム上の要素を制御することができるはず。

■ 実験台

実験台として、以下のフォームを考えてみる。

こんな仕様
  • テキストボックスの名前は "field1"
  • チェックボックスの名前は "check1"
  • ボタンの名前は "button1" で、押下すると "check1"がチェックされている場合は field1 を大文字化した文字列、チェックされていない場合は field1 そのままの文字列を、標準出力に書き出す。

実験台だから単純なフォームにしたけど、実際の現場での開発対象フォームは入力項目が何十個もあって、実行ボタンを押すまでに必要な打鍵動作が多く、1回なら問題ないけど、同じようなテストケースや回帰テストをしたりすると心身ともに疲れたりする。やはり、そういうのは、何とかして省力化したい。

■ 方針

まず、テスト対象フォームを開くと同時に、以下のような別ウィンドウを開く。

で、ここで JavaScript を入力して Excecute ボタンを押下すると、手動の打鍵と同等の JavaScript が実行されるようにしたい。

■ 実装

JavaScript 入力用のウィンドウ ScriptInputWindow は、最初にテスト対象ウィンドウの FrameFixture を受け取る。

public static void main(String[] args) {
   JFrame frame = showWindow(new TestTargetWindow("Test Target"), 0);
   FrameFixture window = new FrameFixture(frame);
   showWindow(new ScriptInputWindow(window), 3000);
}
public static JFrame showWindow(JFrame frame, int xpos) {
   frame.setSize(250, 150);
   frame.setLocation(xpos, 0);
   frame.setVisible(true);
 
   return frame;
}
見ての通りテスト対象ウィンドウは TestTargetWindow だけど、ScriptInputWindow と TestTargetWindow は同じテストハーネスのプロセスから実行する事になる。もしかすると、開発対象システムに固有な何か特別な親ウィンドウとか、ランチャーとかを介さないと、テスト対象ウィンドウ単体ではフォームを開けないと心配する人もいるかもしれないが、大抵の場合は instrumentation ベースのモックツールを使えば何とかなるし、そんなに難しくもない。

ScriptInputWindow はまず最初に、テスト対象フォーム上のコントロールを、JavaScript 用の ScriptEngine に登録する。ScriptEngine も ScriptInputWindow が保持しておく。

public ScriptInputWindow(FrameFixture window) {
   super("JavaScript input window");

   initializeControlls();  

   this.jsEngine = new ScriptEngineManager().getEngineByName("JavaScript");
   registerComponents(window, window.component(), 0);
}

@SuppressWarnings("restriction")
private void registerComponents(
      FrameFixture window, Container container, int indent) {

   for (Component component: container.getComponents()) {
      String name = component.getName();
      if (null != name) {
         ComponentFixture comp = selectComponent(window, name);
         if (null != comp) this.jsEngine.put(name, comp);
      }
      if (component instanceof JComponent)
            listComponents(window, (Container)component, indent + 1);
   }
}
selectComponent() は ContainerFixture から name に一致する ComponentFixture を選んでくるメソッドだけど、面倒なので省略。

ボタンを押すと書き込んだJavaScriptコードを実行する部分は以下のようなもの。

executeButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent arg0) {
      ExecutorService e = Executors.newSingleThreadExecutor();
      e.execute(new Runnable() {
         public void run() {
            try {
               String text = ScriptInputWindow.this.textArea.getText();
               ScriptInputWindow.this.jsEngine.eval(text);
            } catch (Exception e) {
               e.printStackTrace();
             }
          }
      });
   }
});

■ 試行

以下のコードを書き込んで実行すると、テキストボックスの内容が "123abcABC" に変わり、チェックボックスがチェックされ、ボタンが押され、標準出力に”123ABCABC”が出力される。
field1.setText("123abcABC");
check1.check();
button1.click();

■ 応用

  • フォーム上のコントロールの操作以前に、まずコントロールの名前を知ってなきゃならないんだけど、必要な時に階層状に表示してテスターに見せる機能もあった方が良いかもしれない。これは registerComponents でやってる事をそのままどっかに書き出すだけだから簡単。
  • 自動打鍵の結果として得られた画面上の更新項目を、比較しやすい形式でどこか(TextArea とかクリップボードでもファイルでも)に書き出す仕組みもあると便利かもしれない。グリッドとかツリーとかあると、ちょっと手間がかかるかも。
  • 自動打鍵後に、用意しておいた期待値と自動的に比較する仕組みがあると、かなり全自動に近づいてくる。
  • 手動打鍵の記録なんかまでやるとなると、それは難しい。商用製品でも買った方がマシかもしれない(下記の参考記事をみるとFESTでも対応作業中みたいな書きっぷりだけど、どうなったのか不明)。

■ 参考サイト

2011年11月1日火曜日

Eclipse と JMockit でテストファースト

以前のブログで、他の2つのクラスとのコラボレーションを含む簡単なクラスのテストをJMockitを使って書いたけど、その時は、敢えてレガシーコードに後付けテストを追加するような感じで書いてみた。

やろうとしている事は以下のようなものだった。

  1. 作ろうとしているのは Foo というクラスで、文字列を返すメソッド hoge() を持っている
  2. Foo#hoge() が返す文字列は、Baz#hoge() から取得したものである
  3. ただし、Baz のインスタンスは、Bar のインスタンスを経由しないと使えない
これを以下に、テストファーストで実装してみる。ただし今日は、Eclipse を極力活用するという趣向でやってみる。

====

まず①の クラス Foo とメソッド hoge()だけど、以下の様に、まず最初にテストクラスを書く。

public class FooTest {
   @Test public void hoge() {
      Assert.assertEquals("dummy", new Foo().hoge());
   }
}
まだクラス Foo が無いから当然コンパイルエラーの赤波が表示されるので、以下のように仮実装する。
 public class Foo {
   public String hoge() {
      return null;
   }
}
ただし、これをそのままタイプしてはいけない。Eclipse を使うという前提なのだから Ctrl+1を2回(クラスに一回、メソッドに一回)使うだけでタイプせずに済むので、これを活用する。
とりあえず、ここまでで JUnit を実行して、テストが失敗するのを見てから、hoge() の戻り値を"dummy"に変えて、テスト成功に変わるのを確認しておく

で次に、② の Baz#hoge() への委譲。これもテスト・コードから書く。

public class FooTest {
   @Test public void hoge() {
      new NonStrictExpectations() {
         Baz baz;
         {
            baz.hoge(); result = "dummy"; times = 1;
         }
      };
      Assert.assertEquals("dummy", new Foo().hoge());
   }
}
ここでももちろん、Baz も Baz#hoge() も未だ無いわけだから赤波が表示されるけど、さっきと同様に Ctrl+1 を 2回使って Baz の空実装を Eclipseに生成させる。ただし今度は Baz#hoge() の中身を以下の様に書き換える。
public class Baz {
   public String hoge() {
      //FIXME [your user name] Nov 1, 2011
      throw new AssertionError();
   }
}
自分の環境では、この FIXMEコメントから AssertionError送出の2行は、Java エディターのテンプレートに登録してあるので、fixme とタイプして Ctrl+1を押せば、その日の日付とログインしているユーザ名で2行挿入される。

で、赤波が消えたら、また JUnit を実行して Baz#hoge() が呼ばれていないというアサーション例外が上がるのを確認し、Foo#hoge() を以下の様に書き換える。
public String hoge() {
   return new Baz().hoge();
}
書いたらまた JUnitを実行して、今度は緑になるのを確認。

最後に、③ の Bar インスタンスを経由して Baz インスタンスを得るところ。これもやっぱりテストコードから。

public class FooTest {
   @Test public void hoge() {
      new NonStrictExpectations() {
         Baz baz;
         Bar bar;
         {
            bar.baz(); result = baz; times = 1;
            baz.hoge(); result = "dummy"; times = 1;
         }
      };
      Assert.assertEquals("dummy", new Foo().hoge());
   }
}
またここでも Baz#hoge() と同様に、Ctrl+1 をクラスとメソッドにそれぞれ適用して空実装を生成し、FIXME付きのアサーション例外送出コードを書いておく。

赤波が消えたらJUnit実行。Bar#baz() が呼ばれてないってアサーション例外が上がったら、おもむろに Fooを以下のように書き換える。
public class Foo {
   private final Bar bar = new Bar();
   public String hoge() {
      return this.bar .baz().hoge();
   }
}
但し、これも Ctrl+1 (→ Create field 〜)を活用し、無駄なタイピングをせずに編集する。できたら、Junit を実行しになることを確認。

というわけで、完了。

====

ちなみに、この間からかじり始めた Coq のチュートリアルの第2回で、結論から1個ずつ論理を遡って証明を完成させていく手順が紹介されていたけど、テストファーストと似ているところがあると思った。

2011年10月24日月曜日

簡単な後付けテストで基本を再確認

こんなクラスがあるとする。

public class Foo {
   private final Bar bar = new Bar();
   public String hoge() {
      return this.bar.baz().hoge();
   }
}
見ての通り、Foo は メソッド hoge()の中で、関連オブジェクト bar から取得した baz に、hoge() 処理を移譲している(日本語にすると、なんのこっちゃだが…)。

Bar と Baz は以下のような感じで後回しにしてある。

public class Bar {
   public Baz baz() {  throw new AssertionError();  }
}
public class Baz {
   public String hoge() {  throw new AssertionError(); }
}

ここで、Foo#hoge() をテストするにはどうするか。つまり以下の/* ??? */ のところに何を入れたらテストできるか。

public class FooTest {
   @Test public void hoge() {
      /* ??? */
      Assert.assertEquals("hello", new Foo().hoge());
   }
}

とりあえず2個だけ例を書いてみると(ちょっとずつニュアンスの違うものを何通りも書けるが)、以下のようになる。

@Test public void hoge1() {
   new Expectations() {
      @Mocked("baz") Bar bar;
      @Mocked("hoge") Baz baz; {
         bar.baz(); result = baz;
         baz.hoge(); result = "hello";
      }
   };
   Assert.assertEquals("hello", new Foo().hoge());
}
@Test public void hoge2() {
   new NonStrictExpectations() {
      Bar bar;
      Baz baz; {
         bar.baz(); result = baz; times=1;
         baz.hoge(); result = "hello"; times=1;
      }
   };
   Assert.assertEquals("hello", new Foo().hoge());
}
JMockit を使い始めたばかりだと、結構、とまどうんじゃないかと思う。デバッガで追ってみると、例えば "result = baz" のところで、初期化していないはずの baz に何故かインスタンスが設定されているし、代入されたと思った result が null のままだったりして、普通の Java ソースの感覚で読んでいると訳が分からなかったりする。

とは言ってもまあ、すぐ慣れるし、そうなると逆に、JMockit 無しのテストを考える事が、時折難しく感じられてくる。

ただ、JMockit を使うと後付けテストもかなり簡単になるにはなるけど、やっぱりテストファーストが基本というのは押さえておいた方が良い。この例はもともと簡単だから、それほど大変なテストコードにはならないけど、もっと複雑なものになると、後付けとテストファーストではテストコーディングの生産性が大きく違ってくる。

====

テストファースト版は、こっちに書いた。

2011年10月10日月曜日

自動テストのレベル分け

最近は、「xUnit で UnitTest を書いてカバレッジとってます」なんて、どいつもこいつも謳ってるけど、お前らマジかと。それで出来てるつもりなのかと…?

というわけで、秋の朝がさわやかなので、自動テストの出来てる度合いの段階区分を考えてみたい。

  Level 3: 出来てる:常時テスト成功、高カバレッジ

ちゃんとできてるプロジェクトの出来る子ちゃん達からしたら、取り立てて言及することもない当たり前の状態だと思う。

"Clean Check-In" が普通に守られているから、当然、テストも常時全件成功する。まあ基本中の基本。

で、TDD/TestFirst で開発してるから、開発の最初期から高カバレッジ(計測対象範囲内でのカバレッジ基準充足)で始まり、進捗に伴いソースコード量が増えていく中でも、高カバレッジを維持したまま推移する。


Level 2:怪しい:ほぼ常時テスト成功、低カバレッジ

"常時"かつ"全件"、テストが成功してはいるけど、カバレッジが低いプロジェクト。まあ、後付けでテストを書こうとすると、えてしてそんな感じになる。

原因としては、単にスキルがないから仕方なく後付けになったり、確信的にテストをサボっていたり、いろいろあるにせよ、結果的に以下のような感じになる。

①:テスト・コーディングに凄い余計な時間が掛かる。テスト・ファーストでやってさえいれば自然に得られたはずの保守性・メンテ性が備わってない低テスタビリティ・コードに無理やりテストコードを書いてく事になるから、まともな生産性は無理。

② :テスティング・ポイントがウヤムヤになる。本体コードを書いていた時点では脳内にあったはずのコーディング意図がとっくに揮発した状態で、テストコードを後付けする破目になる。何をテストしてるのか本人が分かってない状態。

③ :テストコードがザルになる。①で指摘した生産性の低いテストコードを、②で指摘した曖昧状態のプログラマが書いてくわけだから、「何かをテストする」事ではなくカバレッジを通す事が自己目的化してしまい、結果、Assertion も Expectation も不十分で、単に実行経路に含まれただけの空虚なテストコードになってしまう。

まあ、後付けテストの全てがそんな糞テストコードばかりとは限らないけど、テストコードは本体コードより先に書いとくに越したことはない。それが普通なのだと認識するだけで、悪い事がいろいろ避けられて良い事がいろいろ増えてくる。


Level 1:下手糞:常時テスト失敗、低カバレッジ

低カバレッジでも、取り敢えずテストが存在して差し当たり成功していれば、まだ見どころはある。また、たまたま間違えてテストを壊す事もあると思うが、そんなのすぐ直せば良い事で別に問題じゃない。

だけど、テストが通らないコードをコミットするのが常態化していたり、CIサーバでテストが失敗してるのに放置されていたりしたら、それはかなりの低スキル・チームの疑いがある。

実は、そうしたプロジェクトでは、Level 3 の状態を志してはいたもののスキル不足で健闘虚しくって感じじゃなくて、 Level 1 の状態で普通・正常だと思ってる開発者が大勢を占めていたりする。酷いのになると「今までに経験したプロジェクトではそれが普通だったし、テストが壊れても気にしない方がリファクタしやすい。」なんて事を真顔で主張したりする(リファクタするためにこそテストコードが必要だという最低限の常識を弁えていれば、そうした発言は出ないんだけど…)。

そうやって考えてくと、そもそも技術者の○○使用経験とか○○歴とかって何なのだろうという問題に突き当たるが、これは別の機会に考えてみたい。あと、Level 1 で生じている問題には、余りにも劣化した形で普及し実践されている『リファクタリング』もあるのだけど、これも別の機会に考える。

朝から、気が滅入ってきた。山でも歩いてこよっと。

2011年8月15日月曜日

JMockit: 基底クラスのコンストラクタのすりかえ

基底クラスのコンストラクタ呼び出しだけをモックですりかえるにはどうすれば良いか。

以下のように実験してみた。AssertionError を投げているところを、設定ファイルの読み込みとかマスタデータへの依存とか、面倒なセットアップが必要だったり時間がかかったりする処理に読み替えると、ニーズが分かると思う。

====
例題 (1)
クラス BaseClass を継承したクラス CuT があるとする。こんな感じ。
public class CuT extends BaseClass {

public CuT() {
super();
}
}

ところが、BaseClass の実装はこんな感じだったとする。
public class BaseClass {

protected BaseClass() {
throw new AssertionError();
}
}

以下のテストコードを実行すると、当然、AssertionEror が発生してテストが落ちるが、落とさずにインスタンスを作るにはどうすれば良いか?
@Test public void testCuTConstructor() {

//ここに何を書けば落ちないか
new CuT();
}

答え (1)
以下の様にして、基底クラスのコンストラクタをすり替えられる。
@Test public void $init() {

new MockUp<BaseClass>() {
@Mock void $init() { /*とりあえず何もしない*/}
};
new CuT();
}


例題(2)
例題(1) にちょっと書き足してみる。
public class BaseClass {

protected final int bar;
protected BaseClass(int bar) {
this.bar = bar;
throw new AssertionError();
}
}
public class CuT extends BaseClass {
public CuT(int bar) {
super(bar);
}
public int foo() {
return this.bar * 100;
}
}
@Test public void $init2() {
new MockUp<BaseClass>() {
//ここに何も書かないと、下のアサーションが失敗する
};
Assert.assertEquals(200, new CuT(2).foo());
}
このままだと基底クラスの bar が 2 で初期化されず、foo() が 0 * 100 = 0を返してしまうので、アサーションが失敗する。MockUp の匿名インナー型定義に、何を書き足せばテストが通るか?


答え (2)
コンストラクタ実行中のタイミングで、モック対象インスタンスの bar フィールドに適当な値を設定すれば良いわけだが、そのインスタンスをどう取得するかというのが問題。"it"フィールドという仕組みを利用すれば良いらしい。
@Test public void $init2() {

new MockUp<BaseClass>() {
BaseClass it;
@Mock void $init(int bar) {
Deencapsulation.setField(it, bar);
}
};
Assert.assertEquals(200, new CuT(2).foo());
}
この例では、bar は private フィールドなのでDeencapsulation ユーティリティクラスを使っている。ちなみに bar は final だけど、問題なく設定できている。

2011年8月14日日曜日

JMockit を使って簡単に「後回し」するやり方

二つのクラスがあって、片方がもう片方に依存しているとする。つまり、参照関係や使用関係がある。

両方とも開発対象の場合、常に「依存される」側からしか作ろうとしない/作れない開発者がいるけど、逆の方向、つまり「依存される」側を一旦後回しにして「依存する」側から作った方が見通しが良い場合が多い。

方法はいくつもあるが、今日は JMockit を使って実演してみる。

====
◆ 仕様
依存する側のクラス
クラス CuT:Collaborator を使うクラス。こいつから作る。
 関連オブジェクト:
  col1: Collaboratorのインスタンス
  col2: Collaboratorのインスタンス
 コンストラクタ:受け取った2つの整数によりcol1 と col2 を初期化する
 メソッド:
  int foo(int number):
   col1 と col2 の bar() に number を渡し、結果の合計を返す

依存される側のクラス
Collaborator:CuT に使われるクラス。後回しにする。
 コンストラクタ:受け取った整数を保持する
 メソッド:
  int bar(int number)
   コンストラクタで受け取った整数と引数 number を用いて、何か計算をして返す。


◆ コーディング
両クラスのガラだけ書いてみる。CuT から作ってく趣旨なので、テストクラス CuTTest も書き始める。
public class CuT {}

public class Collaborator {}
public class CuTTest {}

まず、コンストラクタのテストを CuTTest に追加する。CuT のコンストラクタで受け取った引数が、二つの Collaborator のコンストラクタに受け渡されれば良いので、次のようなテストメソッドを書く。
@Test public void $init(final Collaborator mock) {

new Expectations() {
{
new Collaborator(2);
new Collaborator(3);
}
};
new CuT(2, 3);
}

とりあえず、存在しないコンストラクタを呼んでいるのでコンパイルエラーになるから、これを解消する。

まず、Collaborator のコンストラクタだが、こいつは以下の様なコードで後回しにする。
public Collaborator(int i) {

throw new AssertionError();
}

次に、CuT のコンストラクタだが、まずモックがちゃんと効いているか確かめるために、以下のように空の実装を書いて、一度テストを実行してみる。
public CuT(int number1, int number2) {}
テスト実行すると、「コンストラクタが引数 2 で呼ばれていない」といった感じのエラーメッセージが出力されるので、今度は以下のような感じで、ちゃんと書く。
public class CuT {

final Collaborator col1;
final Collaborator col2;
public CuT(int number1, int number2) {
this.col1 = new Collaborator(number1);
this.col2 = new Collaborator(number2);
}
}
再度テスト実行すると、テスト成功。

続いて同様に、まずテストから foo() を書く。

テスティングポイントは、「foo() で受け取った引数を、col1, col2 の bar()に渡している事」と「col1, col2 の bar()の戻り値の合計を、foo() の戻り値とする」なので、以下のようなテストコードになる。
@Test public void foo(

@Mocked(capture=1) final Collaborator col1,
@Mocked(capture=1) final Collaborator col2) {
new Expectations() {{
col1.bar(7); result = 10;
col2.bar(7); result = 100;
}};
Assert.assertEquals(110, new CuT(2, 3).foo(7));
}
Collaborator#bar() の実装が無いので、コンストラクタ同様に AssertionError 送出のみの後回しコードを追加して、コンパイルエラーを解消する。
public int bar(int i) {

throw new AssertionError();
}

CuT#foo() は、とりあえずダミーの固定値を返すようにして、テストを実行してみる。
public int foo(int i) {

return -1;
}

実行すると「bar が引数7 で呼ばれていない」といったエラーメッセージが出力される。期待通りなので、おもむろに foo() の実装を本物コードに修正する。
public int foo(int i) {

return this.col1.bar(i) + this.col2.bar(i);
}
これで、テストがちゃんと通るようになる。

一段落ついたら、クラス CuT の事は忘れて、Collaborator 実装に集中して取りかかればいい。

◆ 補足
ちなみに、JMockit に不慣れで capture の意味が分からなければ、このサンプルの col1 と col2 の capture を変えてテスト実行すれば、capture がどういう働きを持つのか分かると思う(場合によっては Expectations を NonSrictExpectations に替える必要がある)。

例えば、col1 のcapture を2に変えると、foo()の戻り値は20になる。col2 をそのまま変えずに col1 の capture を 0 にすると、foo()の戻り値は100になり、col2 の capture を2にすると、foo()の戻り値は200になる。

テストメソッド CuTTest#foo() のパラメータの col1, col2 が、CuT のフィールドのcol1, col2 のどれに対応するかが、capture で制御されているのが分かると思う。

さらに、もう一つ追記。上の CuTTest#foo()は以下のように書き換えられる。
@Test public void foo_explicitVerification(

@Mocked(capture=1) final Collaborator col1,
@Mocked(capture=1) final Collaborator col2) {
new NonStrictExpectations() {{
col1.bar(anyInt); result = 10;
col2.bar(anyInt); result = 100;
}};
Assert.assertEquals(110, new CuT(2, 3).foo(7));
new Verifications() {{
col1.bar(7);
col2.bar(7);
}};
}

若干、冗長になるが、テストの都合で指定したい動作と、検証したい動作を分けて書いている。実際の業務ロジックなんかで複雑な相互作用がある場合など、Expectations に全部書いてしまうと何がテスティングポイントだったのか分かりにくくなる事がある(特に後付けでテストを書かざるを得ない状況などで)。そんなとき Verifications のブロックに本当に検証すべきだった事を書いておくと、テスティングポイントを見失わずにすむ。

ちなみに、Verifications には、FullVerifications, FullVerificationsInOrder, VerificationsInOrderといった派生クラスがあるから、適当に使い分けるべし。

2011年7月11日月曜日

JMockit と Swing と無名クラス

JMockit を使った Swing の UnitTest を考えてみる。今日はとりあえず、最初の一歩。
参考資料はこれ → Behavior-based testing with JMockit

====
まずこんなコードから始めてみる。Swing に限らず、GUI フレークワークの入門書の最初に出てくるような、何もないウィンドウを単に開いてみるだけのもの。
public class Exercise1 {
  public static void main(String[] args) {
     JFrame frame = new JFrame();
     frame.setSize(500, 300);
     frame.setVisible(true);
  }
}

要するに 500×300のウィンドウがビジブルになれば良い訳で、以下のようなテストコードになる。
public class Exercise1Test {
  @Test public void main() {
     new Expectations() {
        @NonStrict JFrame frame; {
        frame.setSize(500, 300);times=1;
        frame.setVisible(true);times=1;
     }};
     Exercise1.main(new String[]{});
  }
}

ここで、ウィンドウのクローズイベントに対応するアプリケーション終了処理が無い事に気づき、例えば、以下のように追加したとする。
public static void main(String[] args) {
  JFrame frame = new JFrame();
  frame.addWindowListener(new WindowAdapter() {
     @Override public void windowClosing(WindowEvent e) {
        System.exit(0);
     }
  });
  frame.setSize(500, 300);
  frame.setVisible(true);
}
この無名クラスを含むコードをどうやってテストするか。

以下のような方針をとってみた。
  • System クラスをパーシャルモックして、exit(0) 呼び出しを expect する(変な日本語だが…)。
  • 無名インナークラスについては、次のようにする。
    • addWindowListener に渡された、WindowListener を保持しておく。
    • main() 実行の後で、保持しておいた WindowListener のwindowClosing()を明示的に呼び出す。

コードはこんな感じ。
public class Exercise1Test2 {
  @Mocked({"exit"}) System system;
 
  static class WindowListenerCapturer implements Delegate {
     WindowListener captured;
     void addWindowListener(WindowListener l) {
        captured = l;
     };
  }
 
  @Test
  public void main() {
     final WindowListenerCapturer delegate = new WindowListenerCapturer();
     new Expectations() {
        @NonStrict JFrame frame; {
        frame.addWindowListener((WindowListener)any); result = delegate;
        frame.setSize(500, 300);times=1;
        frame.setVisible(true);times=1;
     }};
     new Expectations() {{
        System.exit(0);
     }};
     Exercise1.main(new String[]{});
     delegate.captured.windowClosing(null);
  }
}

ここでは、実際の実行パスの流れと同じになるように、main() からの Expectation と、windowClosing() からの Expectation を分けて書いてみた。

WindowListenerCapturer を使わないで、result = new Delegate() { ... }として、「...」のところで、windowClosing() を呼ぶやり方も試してみたが、何故か Eclipse プラグインがテスト終了を認識しない。

まあ上で示したコードでも、キャプチャのためのコードが若干増えることになるとはいえ、実行時の流れを表していると言う点では、却って分かりやすいような気もする。

2011年7月4日月曜日

JMockit

数日前のポストで、この2・3年に普及してきた、Java の Mocking / Isolation フレームワークに触れた。今日は、そのうちの一つ JMockit を使って、以下のお題について解を考えてみる。

クラス TestTarget があり、Collaborator オブジェクトへの関連と、メソッド methodA() を持っている。

public class TestTarget {
   private final Collaborator collaborator = new Collaborator();
   public String methodA(String pattern) {
      int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
      return String.format(pattern, collaborator.methodB(hour));
   }
}

methodA() では、Collaborator オブジェクトのメソッド methodB() を呼んでいるが、methodB() の振る舞いについて分かっている事は、24時間制の「時」を表す整数を与えると、その時刻を表す単語("morning"や"夕方"など)を返すと言う事だけである。

さて、ここで methodA() における、Collaborator オブジェクトとの協調を含む、TestTarget の振る舞いをテストするコードはどのようなものになるか。

見ての通り、methodA() の振る舞いは、
  1. Calendar から現在時刻の「時」の部分を取得し
  2. これを Collaborator.methodB に渡して戻り値を受け取り
  3. 更にこれを書式化して返すというものになる。

テスティングポイントは以下のようになる。
  1. Calendar から、正しく「時」を取得している事
  2. collaborator に渡された「時」が、上記1で取得したものである事
  3. collaborator から返された文字列を正しく用いて、メソッドの戻り値を作っている事

また、以下のような事に留意する必要がある。
  • Collaborator.methodB() の現時点の挙動には依存しない事。
    時間の区切りがどうなっているか、言語が何であるかなどは、今後変わる可能性があるが、TestTarget クラスの責任範囲外である。
  • Collaborator.methodB() の実コードを呼ばない事。
    Collaborator は外部サービスが起動している事に依存していて、実行時間も無視できない。また TestTarget のテストで Collaborator コードまで呼ばれてしまうと、コード・カバレッジが実際を正しく反映しなくなると言う問題もある。
  • このテストをどの時間帯に実行しても結果が変わらない事。
    つまり、Calendar が返す時刻に依存してはいけない。

この UnitTest を、従来の dynamic proxy ベースの ツール(jmock など)を使ったり、ましてや状態ベースの普通の JUnitテストで書こうとすると、難しいテストコーディングになったと思う。

JMockit を使うと以下のような感じになる。
public class TestTargetTest {
   @NonStrict Collaborator collaborator;
   @Mocked({"getInstance", "get(int)"}) Calendar mockCalendar;
   @Test public void methodA() {
      final int HOUR = 7; 
      new Expectations() {{
         collaborator.methodB(HOUR); result = "morning";
         Calendar.getInstance(); result = mockCalendar;
         mockCalendar.get(Calendar.HOUR_OF_DAY); result = HOUR;
      }};
      String actual = new TestTarget().methodA("good %s!");
      Assert.assertEquals("good morning", actual);
   }
}
コードの意味は以下のようなもの
  • Calendar.getInstance() が モックの Calendar を返すように、getInstance() コードを差し替えた
  • Calendar のモックは、今、何時なのか尋ねられる事を期待し、またその際、固定値7 を返すよう記述した。
  • Collaborator のモックが、固定値7 で methodB()が呼ばれることを期待し、その際、固定値"morning"を返すよう記述した
  • 上記設定で、TestTarget.methodA()に"good %s!"が与えられ、"good morning"が得られる事を検証するよう記述した。


一度グリーンにしてから、いろいろコードを変えてみて期待通りにレッドになることを観察してみた。

せいぜい jmock の延長くらいかと思っていたら、使用感はかなり異なっていて、若干戸惑う。ただし、状態変化ベースだけではなく、相互作用ベースの UnitTest まで理解していれば、それほど高いハードルではない。DynamicProxy 系のテストを書いていていろいろ悩んだり困った経験のある人ほど、理解が早いと思う。

2011年6月27日月曜日

java の static メソッドの mocking

しばらく .net 案件とかやってた間に、いつの間にか Java でもstatic メソッドに mocking が適用できるようになっていたらしい。

ググってみると、今のところ PowerMockJMockit が、どうやら使えそうな感じ。

前に Java をやっていた頃、mocking には jMock(いわゆる「流れるようなインターフェイス」が何とも素敵な奴だったのだが)を使っていたが、この jMock の制約のせいで java では static メソッドは迂回もすり替えもできないと諦めていた(final 外しはあったが)。

考えてみれば、.net の Mole も TypeMock も instrumentation を使っていたわけだけど、Java だって 5.0 からは instrumentation があったんだから、とっくにできていて不思議はなかった。

しかし、どっちを使えば良いか悩む。

つうか、昔と同じく jMock を使うという選択肢も捨てきれないから、3択になる。いや、PowerMock は EasyMock か Mockito と組み合わせるからもっと選択肢が増える(jMock との組み合わせもできないことないらしいが、ちょっと不安)。

カバレッジツールとの相性とか、微妙な使い勝手の優劣とかもあるだろうし、急いで決めねばならない状況ではあるんだけど、拙速な決断は危ない。

うーん、もっと早く気づいていればなあ…

2011年6月11日土曜日

なぜ UnitTestが生産性を上げるか

前回、UnitTest の必要性について書き始めて、どうやらコーディングの生産性向上らしいってとこまで来た。今回はその続き。

====
UnitTest はコーディングの生産性を高める。

・・・というのは少し大雑把で、より正確には、高い生産性を得るために最も重要な条件の成立に、UnitTest が不可欠であると言う事になる。

で、その条件とはすなわち「内部品質が高い」と言う事である。

この内部品質を高める事により、「ソースを読む」、「書き足す」、「書き直す」、「間違いを探す」と言った、コーディングの大部分を構成する作業に要する時間を大幅に削減できる。内部品質が高まるほど、コーディングの所要時間が下がり、生産性が上がってくる。

では、なぜ UnitTest が内部品質の向上に寄与するのか?

これは、UnitTest が内部品質向上作業の生産性を高めるからなのだけど、この「内部品質向上作業」とは、ザックリ言うとリファクタリングの事である。

このリファクタリングを徹底的に(Mercilessly)に行う事により、高度な生産性を実現できるような、高いㇾベルの内部品質に初めて到達できる。ここで UnitTest は、リファクタリング作業それ自体の生産性を高めるために(同時にリスクを下げるために)利用される。

これ以上の説明は不要な気がしないでもないが、なぜ UnitTest によってリファクタリング作業の効率性が高まるかというと、コード修正による「意図せざる事象」をもっとも効率よく検出できるからと言える。
前にも書いたが、UnitTest とは「doing the right things(正しい動作をしている事)」ではなく、「"doing things right"(正しく動作している事)」を検証するものである。ここで「正しく」とは、「意図した通り」という意味なわけで、つまり UnitTestとは「意図せざる事象」を発見する仕組みという事になる。

リファクタリングという作業は、極論すれば仕様なんて知らなくても良いとうホワイトボックスの中身だけに着目した作業と言える。また、プログラムの成長に伴い、随時(常時)、実施していくものなので、必然的に反復作業となる。

従って、外観の振る舞いを変えずにソースコード修正を繰り返すリファクタリングという作業に当たっては、自動化ホワイトボックステストである UnitTest が最適なプラクティスということになる。FunctionalTest でも結果的に「意図しない動作」を検出できないでもないが、直接性において UnitTest に比べて劣る。

ちなみに、そもそも JUnit を用いた UnitTest が広く一般に認知されたのは、ファウラーのリファクタ本で紹介されたのが契機だったりするので、歴史的にも UnitTest は リファクタリング と密接に関連付けられたプラクティスだと言える。(JUnit を使う事が UnitTest であるとは必ずしも言えないが)

以上、コーディング生産性向上と UnitTest の関係についてだいたい説明したが、振り返ってまとめると、以下のようになる。
UnitTest を充分書く

リファクタリングの生産性が高まる

内部品質が向上する

短い時間で正しいコーディングが簡単にできるようになってくる


これは、以下のように逆に書くこともできる

UnitTest を書かない

リファクタリングできない

内部品質が低下する

頭をかきむしりながら長時間かけてるのにバギーなコード


====
実際の現場では、逆に書いた版のプロジェクトが、未だに多いんだよなあ。

UnitTest をしないからリファクタできないパターンと、リファクタリングをしない(或いはそもそも知らない)から、UnitTestの必要性が分からないってパターンの両方考えられるが、どっちにしても低レベルすぎる。

といったわけで、内部品質の劣悪なソースコードと UnitTest の不履行との間には高い相関があるわけだけど、内部品質だけじゃなく外部品質、つまりバグやらパフォーマンス劣化やらにも巡り巡って因果関係が及んでいたりする。
そのうちこれも書こうと思う。

なぜ UnitTest が必要か

以前、2種類の品質、製品品質とコード品質について対比した。
また FunctionalTest と UnitTest についても違いを比べた。

前回は、製品品質の保証のためには、FunctionalTest が必要であることを説明したが、それらを踏まえて、今回は UnitTest の必要性とは何かを、考えてみる。

====
UnitTest を書く理由、得られる効用について考えると、テストって語が含まれてるぐらいだから、品質向上のためじゃなかろうかと最初は思う。

でも違う。仕様通りに動くかどうかといった製品品質(外部品質)の検証なら、前回書いたように、UnitTest ではなく FunctionalTest の出番になる。また製品品質ではなくソースコード品質(内部品質)の検証だとしても、静的チェックツールがふさわしい。どちらの意味の品質についても、UnitTest の出番ではない。

品質じゃないとしたら、何だろう。UnitTest といえば、自動化テストなわけだから、つまり生産性向上?

うん、生産性の向上である事は間違ってなさそうだけど、でも何の生産性か。ソフトウェア開発にはいろんな作業が含まれるが・・・

ならば、直訳で「単体テスト」だから、単体テストの生産性の向上?

うーん、いわゆる「単体テスト」、つまり旧来のウォーターフォール・モデルで言うような、「一個のモジュールが、後工程の結合テストを実施するに足る品質を満たしているかどうか検証する作業」という意味なら、やっぱり違ってるだろう。

「単体→結合」の観点で見た場合に、そこで単体に要求されている品質は、単体レベルの機能要求がブラックボックス的にで満たされていればそれで良い訳で、これを検証するのは FunctionalTest という事になる。繰り返しになるが、UnitTest は品質(機能・非機能要求の充足)、つまり"doing the right things"は検証しない

単体テストじゃないし、設計でもなさそうだし、そんじゃ、コーディングの生産性か・・・

まあ、結局そういうことになる。

がしかし、そもそもテストを書くという追加作業をしてるのに、生産性が上がるのはなぜだろう。それにコーディングといってもいろいろあるし、どの局面の何のコーディングだろう。

====
長くなったので、次に続く

2011年6月8日水曜日

FunctionalTest の必要性

以前、 UnitTest と FunctionalTest と の違いについて、「コードが正しく書かれている事」のテストと、「コードが正しく動いている事」のテストとして対比した。

今日は、前者の FunctionalTest について例を上げて説明し、その必要性について考えてみる。

例えば、こんな問いがあるとする。

数字を受け取って平方根を返すプログラムを書いた。
ところが、平方根ではなくて二乗が返されるというバグが出た。
さて、どんなテストを書いておけば、これを防げたか。

最近 xUnit や TDD を始めたばかりの人なら、「xUnit で UnitTest を書いてカバレッジを 100%にしておけばよかったんじゃない?」なんて考えるかも知れない。

だけど、xUnit の実践経験がそこそこある人なら、それじゃ十分ではないとすぐ分かると思う。

これが単なるコーディングミス、つまり「正しく仕様を理解した上でのコードの書き間違い」によってのみ起因するバグならば、例えば 4 から 2が得られる事を確かめるアサーションがテストコード中にあるはずなので、誤りはすぐに検出される。この場合は、上の答えでもあながち間違いではない。

ところが、同じバグは単なるコーディングミスによるのではなく、「誤った仕様理解の下での意図通りのコーディング」によっても生じてくる。

例えば何かの拍子で、プログラマが平方根と二乗を勘違いして取り違えていた場合、本体コードに二乗の計算が書かれるのみならず、テストコードにも 4 から16を返すアサーションが書かれてしまうので、バグは検出されない。

平方根と二乗の勘違いなんて何だか馬鹿馬鹿しい例だが、実際のシステム開発のドメイン(問題領域)は、普通はもっと複雑だから、こうした認識ズレがバグの原因に占める割合は非常に多い。(また、上のような概念Aと概念Bの取り違えの他にも、仕様の見落としや失念、変更の連絡ミスなど、様々な事が認識ズレの原因になる。)

一般に、「要求されている仕様」と「実装者の認識」のズレから生じたバグは、テストコードにも同じ誤認を織り込んで書かれてしまっているため、本体のコーディングと連動して生産された UnitTest によっては検出できない。

この種のバクを防ぐためには、仕様を理解し責任を持っている人が作業に介在しなければならない。

仕様担当者がプログラマが書いたテスト仕様書をレビューしたり、仕様担当者自身がテスト仕様書を作ったりといった形で、「わかってる人」の意識をくぐり抜ける必要がある。上の例では、「平方根を返す」という要件を定義した人が、テスト仕様書に「4 ならば 2」という「条件 ---> 結果」のペアが含まれている事を確認していなければならない。

つまり最初の問いの答案としては、仕様が分かっている人にレビューされた仕様ベースのテスト、つまり FunctionalTest が必要ということになる。

(ちなみに、テスト仕様書の作成はコーディング前でも良いし、またテスト実施に自動化ツールを使ってもいい。ただし、ソースコードベースの DeveloperTestとは視点が全然違うことに注意する必要がある。例えば、網羅率はコードカバレッジではなく、仕様から起こしたテスト項目の実施率となる。)

====
といったような事は実はかなり基本的な事項なんだけど、かなり多くの底辺現場では理解されていない。で、「コードカバレッジが100%に近づいているのにバグが減らない。って事は、xUnitや TDD は役に立たないって事になるんじゃね?」なんて、アホな論理がまかり通るようになる。やれやれ…

2011年2月1日火曜日

Test Double の「Double」とは

プログラマにとって「double」と言えば、まず第一に倍精度浮動小数点数のことだろうけど、Test Double の「double」はどうか。

昨今の開発で、まともにテスト駆動が成立するレベルのテストコード、つまり TestSmell が一定以下に抑制された DeveloperTest を書いていくためには、かなり多くの場面で Test Double パターンが必要になる。今日びのプログラマにとっては、Stub、Mock、Fake、Spy 等を含む Test Double と言われる一群のテストパターンを自在に使いこなす事は、必須技術の一つと言えるんじゃないかと思う。

個人的な話になるが、実は自分は、この Double の意味を知らないまま、長い間「Test Double」という言葉を使っていた。深く考える訳でもなく、何となく「Test」を動詞、「Double」を副詞と認識して「二重にテストする」なんて意味かなと思っていたが、TestDouble が意味する手法と一致しなくて、ずっと気持ち悪かった。

ある時、何気なくネットの辞書を引いてみたら、ちゃんと答えがあった。

Weblio の英和辞書ではこんな感じ
【名詞】3.b【映画】
代役,替え玉 〔for〕.

英英辞書の OALD ではこんな感じ
NOUN PERSON/THING [countable] 
an actor who replaces another actor in a film/movie
to do dangerous or other special things

まず、Double を副詞と考えていたのが、そもそも間違いで、本当は映画用語の名詞。Double が副詞でないということは「Test」は動詞ではありえず、したがって「Test Double」で名詞+名詞の複合名詞、つまり「テストの替え玉」という意味になる。

やっぱ辞書はこまめに引かないといかんな。

※OALD は 非英語ネイティブ向けの英英辞書としては、かなり良い感じ。ネット版だけじゃなく、リアルの辞書もよくできてる。

2011年1月25日火曜日

GUI コードのUnitTest の例

こんな感じのしょうもないフォームがあるとする。 DataGridView があり、A列と B列は数値を入力できる列で、C列は A - B の計算結果を表示する。 さて、このフォームには、C列の値が負のとき赤で表示するという振る舞いがあるが、果たしてこれをテスト駆動で書けるだろうか? TDD に慣れた人なら、普通にテストファーストで書くだろうけど、ハナからできないと決め付けてる人も意外と多い。 そんな人が考えるテストはこんな感じ。
  • アプリケーションを起動する。
  • A列、B列に値を入力する。
  • C列の前景色が変わったことを確認する。
まあ普通の手動の動作確認と同じだけど、これを自動化テストコードとしては書けないから UnitTest は無理と言う。 実際にはそういったテストコードも書けないことはないが、TDD の UnitTest ではそんなのは書く必要ないし、第一、テストの観点も目的も手法も違ってるから、むしろ書いてはいけない。 前に書いた HumbleView の作法でやると、例えばこんな風になる。
[TestFixture]
public class Form2PresenterTest
{
    public Form2Presenter presenter;
    public Mock<IForm2> mock;

    [SetUp]
    public void SetUp()
    {
        this.mock = new Mock<IForm2>();
        var view = mock.Object;
        this.presenter = new Form2Presenter(view);
    }
    [Test]
    public void SelectStyle_Plus()
    {
        TestSelectStyle(10, 5, CellStyle.NonNegative);
    }
    [Test]
    public void SelectStyle_Zero()
    {
        TestSelectStyle(10, 10, CellStyle.NonNegative);
    }
    [Test]
    public void SelectStyle_Minus()
    {
        TestSelectStyle(10, 20, CellStyle.Negative);
    }
    public void TestSelectStyle(int a, int b, CellStyle style)
    {
        //arrange
        var rowData = this.presenter.RowDataAt(0);
        rowData.A = a;
        rowData.B = b;
        //act
        this.presenter.Update(0);
        //assert
        mock.Verify(v => v.SetGridStyle(2, 0, style));
    }
}
見ての通り、Update()メソッドで行のインデクス(簡単のため0固定とした)を受け取った Presenter が、更新された A 列とB列の値に基づいてスタイルを選んで、セルの位置とともにビューに返すという流れになる。 これを満たす IForm2 と Presenter のコードはこんな感じ
public interface IForm2
{
    //・・・略・・・
    void GridDataSource(List list);
    void SetGridStyle(int col, int row, CellStyle styleId);
}

public class Form2Presenter
{
    //・・・略・・・
    public void Update(int rowIndex)
    {
        var rowData = this.list[rowIndex];
        var cellStyle = rowData.C < 0 ? 
                CellStyle.Negative : CellStyle.NonNegative;

        this.view.SetGridStyle(2, rowIndex, cellStyle);
    }
}
やり方はいくつかあるが、ここでは色を System.Drawing.Color で直接指定する代わりに、列挙値 CellStyle を定めて使っている。この CellStyle 定数と実際にセルに設定する DataGridViewCellStyle オブジェクトのマッピングを保持しておくクラスも別途必要だが、自明なので省略。 この辺りまで書くとテストも通る。 後は、View と Presenter を関連付けるだけ。
public partial class Form2 : Form, IForm2
{
    //…略…
    private void dataGridView2_CellEndEdit(
        object sender, DataGridViewCellEventArgs e)
    {
        this.presenter.Update(e.RowIndex);
    }
    public void SetGridStyle(int col, int row, CellStyle styleId)
    {
        this.dataGridView2[col, row].Style = CellStyleMap.StyleOf(styleId);
    }
}
前回と同様、View オブジェクト(Form2)は受身で丸投げなクラスで、余りテストの必要性が高くないほんの少量のコードを残して、その他の振る舞いのコードを Presenter に移した。 こんな風に、一見いかにも GUI の最表層みたいな振る舞いでも、フォーム=View を薄く薄くしていく事で、妥当な網羅性を備えた UnitTest が書けるようになる。

2011年1月23日日曜日

GUI コードのUnitTest の基本形

こんなフォームがあるとする。


ツールボックスからコントロールを持ってきて置いただけの状態だが、これを
右のボタンを押すとメッセージボックスが開いて、OK すると文字列"OK"、Cancel すると文字列"Cancel"が、左のテキストボックスに表示される
ようにしたい。

さて、この作業を TDD でやるとどうなるか。

GUI アプリは UnitTest できないものと考えがちな人は、やはりここでもメッセージボックスに伴うユーザ・インタラクションを理由に UnitTest を諦める。

実は、こういった場合の対処法は割と昔から研究されて、パターンとして広く知られているものも既にいくつかある。

基本的な方針は、最小限のコードを残して、振る舞いに関する責務をテスタビリティの高い別のオブジェクトに移動してしまうというもの。

そうやって、極薄にしたフォーム・コード(UnitTest はオプション)と、振る舞いを含む実質的な UI ロジック(カバレッジ100%)に分けることで UI 層全体としてのコード・カバレッジを満足のいくものにする。

いろいろな変種パターンがあるが、さしあたり以下のように単純に実装してみる。
  • Form1 = view
    • あくまでも受動的に動くフォーム・クラス。
    • Presenter の指示により MessageBox を表示するものの、何も考えずに結果を Presenter に返すだけ。
    • イベントは Presenter に丸投げする。ただし無駄なパラメータは渡したりしない。また、フォーム上のコントロールのプロパティへのアクセッサを、必要最小限な分だけもつ。
  • Form1Presenter = presenter
    • 次のような UI の振る舞いを担う。
      • View にメッセージボックスを表示させて、結果を受け取る。
      • 受け取った結果に応じた文字列を View に表示させる。

また、Presenter から Form1 へのアクセスは、以下のようなインターフェイスを通じて行う。
public interface IForm1
{
string ResultText { get; set; }
bool AskOkOrCancel();
}
※Presenter も interface を実装するような形にもできるが、オプション。

まずは先にテストコードから
[TestFixture]
public class Form1PresenterTest
{
private Form1Presenter presenter;
private Mock<IForm1> mock;

[SetUp]
public void SetUp()
{
this.mock = new Mock<IForm1>();
this.presenter = new Form1Presenter(mock.Object);
}
[Test]
public void HandleButtonClick_OK()
{
TestHandleButtonClick(true, "OK");
}
[Test]
public void HandleButtonClick_Cancel()
{
TestHandleButtonClick(false, "Cancel");
}
public void TestHandleButtonClick(bool isOk, string resultText)
{
//arrange
mock.Setup(v => v.AskOkOrCancel()).Returns(isOk);
//act
presenter.HandleButtonClick();
//assert
mock.VerifySet(v => v.ResultText = resultText);
}
}
※モックはMoqを使った
  • ビューに聞いてこさせたユーザの意図(isOk)に応じて、期待通りの文字列(resultText)が選択され、ビューに設定されることを検証している。
  • 見ての通り MessegeBox もコントロール・オブジェクトも関係ないコードになっている。
  • 実際には、いっぺんにテスト・コードを書いてしまわないで、ちょこっとテストを書いては本体コードを書くというような形になる。
で、その本体コードは以下
public class Form1Presenter
{
private readonly IForm1 view;
public Form1Presenter(IForm1 view)
{
this.view = view;
}
public void HandleButtonClick()
{
this.view.ResultText =
this.view.AskOkOrCancel() ? "OK": "Cancel";
}
}

ここまで書けば取りあえずテストが通る。あと残ったのは、放置していた Form1 を含む Presenter との連携コード。

こんな感じでフォームを実装する。
public partial class Form1 : Form, IForm1
{
private Form1Presenter presenter;

public Form1()
{
InitializeComponent();
}
public bool AskOkOrCancel()
{
return DialogResult.OK == MessageBox.Show(
"OK or Cancel", "", MessageBoxButtons.OKCancel);
}
public string ResultText
{
get { return this.textBox1.Text; }
set { this.textBox1.Text = value; }
}
internal void Attach(Form1Presenter presenter)
{
this.presenter = presenter;
}
private void button1_Click(object sender, EventArgs e)
{
this.presenter.HandleButtonClick();
}
}
この他、フォームを表示するところ(Program.csあたり)で、view を Presenter と関連付けるコードが必要だが割愛。

こんな風に、実フォームでは MessageBox.Show() 等のユーザ・インタラクションと、コントロールへの必要最小限のアクセッサのみを書く。

なんだか、もとのお題が小さすぎるから、コードを切り出したにもかかわらずフォームのコードが増えてしまっているが、実際の業務要件を満たすようなフォームだと、イベント・ハンドラに全部書くようなスタイルよりスッキリしたコードに、ちゃんとなる。

こうしたフォーム・コードは、コード量も多くならないし更新の度合いも少ないし、テストの必要性はそれほど高くない。UnitTest を省略していい部分だと思うが、どうしても DeveloperTest を書きたければ TypeMock か Moleを使えばいい。後付でも問題ないし、テスト・コードも極簡単なものですむ。

さらにフォームのコードを少なくするやり方もあるが、次の機会に。