« オープンソースへの貢献と職務著作 | トップページ | モンティ・ホール問題 »

2008年2月15日 (金)

テスト駆動開発はユニット・テストを駄目にする?

InfoQ の記事より。

Peter Ritchie recently  raised concern about what he considers a tendency for adherence to TDD and BDD to keep practitioners from writing good unit tests. In particular, it is the mantra of "interaction testing" that he suggests has the effect of creating incomplete unit tests; tests that fail to show proof that a unit - an object - works under any conditions it could potentially be used.

InfoQ: TDD/BDD Leading To Incomplete Unit Tests?

テスト駆動開発 Test Driven Development(TDD)/Behavior Driven Development(BDD)が奨めるプラクティス − 最初に、オブジェクトのインターフェイスに対してユニット・テストを書き、テストが失敗するのを確認し、オブジェクトを実装し、テストが成功するのを確認する − は、ちゃんとしたユニット・テストを作るのを妨げている、という話のようですね。

確か、”Behavior Driven Development” というのは、”テスト駆動”という言葉では誤解を招く、TDDで作成しているのは、ユニット・テストではなく、ソフトウエア・インターフェイスの”実行可能な仕様(要求)”である、という主張であったと思います。テスト駆動で開発することのメリットとして、インターフェイス仕様を明確にできる、というのは最近よく耳にする主張かと思います。ソフトウエア・デザイン的な部分を強調する方向性ですね。

元記事(の元記事になりますが)が主張しているのは、そうして作成した、一連のユニット・テスト群は、それがテストであることを明確に意識しないで作成されるがために、 テストとしては無価値になるのではないか、ということのようでして。逆に”テストとしての価値”を問い直しているようです。

TDD派からはいろいろ突っ込みが入りそうです。ちょっと、無理筋な感は否めませんけども(「それとこれとは話が別」とかね)、 ユニット・テストというものの位置付けを考える上で、必要な視点だと思います。それがテストでないなら、テストはどこでやるのか、開発工程上どう位置づけるのか、”仕様としての”ユニット・テストと、”テストとしての”ユニット・テストは、それぞれ別と考えるべきなのか、同じと考えて良いのか、といったことです。テストとプログラミングを同時にやろうとすると、注意が散漫になり、結局テストが中途半端になる、というのはわかるような気もします。

しかし、元記事の元記事で、 Wikipedia BDDエントリ から以下のサンプル・コードを取り上げていますけど、これは、あまりフェアじゃないですね。

public class PrimeNumberCalculatorTests extends junit.framework.TestCase {
   public void testIfPrimeAfter100() {
      PrimeCalculator calculator = new EratosthenesPrimesCalculator(100);
      int result = calculator.nextPrime();
      assertEquals("First prime after 100 should be 101 but is " + result, 101, result);
   }

   public void testIfFirstPrime() {
      PrimeCalculator calculator = new EratosthenesPrimesCalculator();
      int result = calculator.nextPrime();
      assertEquals("First prime should be 2 but is " + result, 2, result);
   }

   public void testIfPrimeAfter683() {
      PrimeCalculator calculator = new EratosthenesPrimesCalculator(683);
      int result = calculator.nextPrime();
      assertEquals("First prime after 683 should be 691 but is " + result, 691, result);
   }

   public void testFirst10Primes() {
      PrimeCalculator calculator = new EratosthenesPrimesCalculator();
      int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 };
      for (int i = 0; i < primes.length; i++) {
         int result = calculator.nextPrime();
         assertEquals("Expected prime: [" + primes[i] + "], got: [" + result "]", primes[i], result);
      }
   }
}

このサンプル・コードに対して、以下のように書いてます。

The EratosthenesPrimesCalculator constructor interface accepts (or seems to) a signed integer.  The tests detailed only test 13 of 4,294,967,296 possibilities.  These tests may very well test the expected behaviour of one system, but don't really test EratosthenesPrimesCalculator as a unit.

Testing the Units (Peter Ritchie's MVP Blog)

4,294,967,296 の可能な入力に対して、たった 13 の入力しかテストしてない、って... まあ、そりゃ、40億くらいの入力なら全部テストできなくはないでしょうけどね。 素数表を使って、テストコードを書けば、簡単にできるでしょうし。入力が大きすぎるなら、ランダム入力でも良いかもしれません。でも、しょせんは、サンプル・コードなんですから、それじゃ、BDDのサンプル・コードとしてはねえ、といいたくなります。

ただ、このサンプル・コードのテスト・ケースが 適当すぎる のも確かですねえ。私なら、テスト・ケースはこんな感じにしますかね。

1. プログラムが処理可能な最大の素数

2. 最大の素数 + 1

3. 最大の素数 - 1

4. 適当な大きさの素数

5. 適当な大きさの素数 + 1

6. 適当な大きさの素数 - 1

7. 2, 1, 0, -1

8. プログラムが処理可能な最大の正の整数

9. プログラムが処理可能な最小の負の整数

・・・

おまけ

テストに関する本には、あまり載っていないのですが(おそらくは、あまりに初歩的すぎるから)。テストケースの作成に関する基本の基本について。

問題

IF A THEN B

上のようなプログラムがあったとして、テストケースはどのように作るべきでしょうか?

解答

まずは、

命題: Aならば、Bである

が真となることを確かめます。つまり、Aを入力してBが出力されるのを確認します。

次に、この命題の対偶命題が真となることを確認します。

対偶命題: Bでないなら、Aではない。

つまり、出力がBとならないような入力で、A以外のものを試します。なお、これは”not A”であるとは限りません(裏命題は必ずしも真ならず)。

|

« オープンソースへの貢献と職務著作 | トップページ | モンティ・ホール問題 »

コメント

コメントを書く



(ウェブ上には掲載しません)


コメントは記事投稿者が公開するまで表示されません。



トラックバック

この記事のトラックバックURL:
http://app.f.cocolog-nifty.com/t/trackback/80472/10515822

この記事へのトラックバック一覧です: テスト駆動開発はユニット・テストを駄目にする?:

« オープンソースへの貢献と職務著作 | トップページ | モンティ・ホール問題 »