どのようにして異常を最も効果的にテストしますか?

2406 ワード

仕事の中で、同僚と異常をテストする最善の方法に違いが生じた.
私はJUnit 4の@Test(expected=FooException.class)を比較的に鑑賞したのですが、こんなに爽やかだと思います.どんなにdeclarativeですか.そんなに大きなtry-fail-catchを書く必要はありません.
しかし、同僚(以下Sと略す)はそうは思わない.彼はtry-fail-catchがいいと思っています.価格が安くて、量も十分で、私たちはずっとそれを使っています.JUnit 4とTestNGが提供するこの機能は、プログラマーがエラーを犯すように誘導しやすい.
Sは挑戦を与えた.

public void testDoSomethingBad() {
  initializeSomething();
  try {
    doSomethingBad();
    fail();
  } catch (FooException e) {}
}

ここでinitializeSomething()の役割は、ある状態に初期化することであり、このプロセスはエラーではなく、この状態になってからdoSomethingBad()が異常を放出することになる.
そして彼はこのような状況が最も普遍的な状況だと主張した.annotationでは美しく見えるが、プログラマーを邪悪に誘惑して不正確なテストを書く可能性があり、false positive、例えばinitializeSomething()が異常を投げた.
もちろん、私たちはこのような状況のよくある程度について自分の意見に固執しています.何も言わない.しかし、後で私は考えて、実は、このテストは自然言語の表現に変えて何ですか?たぶんそうでしょう.
  • initializeSomething()エラーは許されません
  • initializeSomething()の後でdoSomethingBad()エラー
  • では、なぜこの2つの要求を2つのテストに書かないのでしょうか.
    
    @Test
    public void testInitializeSomething() {
      initializeSomething();
    }
    
    @Test(expected=FooException.class)
    public void testDoSomethingBadAfterInitializeSomething() {
      initializeSomething();
      doSomethingBad();
    }
    

    テストを書くときに「賢く」実現するのではなく、必要を素直にコードで表すだけで大丈夫ではないでしょうか.
    それに私はどうしてこのtry-fail-catchがこんなに嫌いなのか.それは私の深い欠点がいくつかあります.
  • コード内の論理ブランチに等しい.投げ異常がなければfail()を実行し、投げ異常があればcatch()に入ります.テストの論理分岐は味が悪い.それはあなたのコードをエラーしやすくします(例えば、failを忘れたらどうしますか?テストは同じように緑ですが、あなたのバグはまだそこに隠れています).また、テストコードが100%のブランチオーバーライド率に達しないようにします.そもそもannotationを使うと、initializeSomething()が異常を投げ出す場合、カバー率はすぐに100%ではなく、問題を簡単に見つけることができます.
  • 冗長で煩わしい.テストはspecではなく、プロシージャコードのように書かれています.
  • このtry-fail-catchは、Exceptionをチェックするときにのみ成立します.もしあなたがErrorやJUnitのAssertionFailedErrorをチェックしたら、あなたはfail()の投げた異常さえ捕まえました.
  • 今朝、急に気がついたが、実は、もう一つの方法があった.たとえば、独自のBaseTestベースクラスでは、expectException()の関数を実装し、次のように使用できます.
    
    public void testDoSomethingBad() {
      initializeSomething();
      expectException(FooException.class);
      doSomethingBad();
    }
    

    このようにrunTest()が終了する前に、exception expectationが存在するかどうかをチェックし、存在する場合はcatchが投げ出した異常を止めてチェックすることができます.異常がなければ、直接間違いを報告します.これで大丈夫じゃない?さらに抽象的に、ExceptionExpectationのインタフェースを作ることで、お客様のコードを柔軟に登録し、異常のタイプやエラー情報をチェックするだけでなく、任意の異常期待を再利用することができます.
    もちろん、これはJUnit 3.8です.