JUnit Sucks


詳細
JUnitを例にTDDを説明する本があるようです.TDDといえば絶対いいものですが、TDD自体は良いソフトを作る保証はありません.いいえ、Junitは生きている例ですね.
ずっとJunit+Easymockテストを書いてきました

public class SomeTest extends TestCase {
   private final IMocksControl control = EasyMock.createStrictControl();
   private final Connection connection = control.createMock(Connection.class);
   @Override protected void tearDown() {
     control.verify();
     // other cleanups.
   }
   public void test1() {
      expect(connection.createStatement()).andReturn(null);
      ...
      control.replay();
      ...
   }
}

具体的なmockの使い方を細かくしないでください.私はすべて臨時編です.しかし、このテスト類には2つの肥腸の深刻な問題がある.そういえば私はずっとそうしていたので、給料をくれたボスたちに申し訳ありません.
最初の質問:
JUnitではfield initializerを使うべきではありません.あるいは、構造関数でtest fixtureを初期化することはできません.new TestSuite(SomeTest.class)を呼び出すと、JUnitシステムはtestSomething()関数ごとにコンストラクション関数を呼び出し、独立したtest instanceを生成する.これにより、各テストの独立性が保証され、あるtest caseが他のtest caseに影響を及ぼすことを最大限に回避することができる.
でも、「待って」と言ってください.これはちょうどいいのではないでしょうか.各test caseはこれらのfield initializerを呼び出しますが、何の問題もありませんか?
そうですね.だから私は何年も真っ直ぐに、厚かましくこのidiomを使っています.このidiomを使っています.最近まで、同僚のjava専門家は私に教えて、これは安全ではありませんて、不道徳で、低級な趣味です!
JUnitはtest caseを実行せずにtest suiteを構築するときにこれらのコンストラクション関数を呼び出したため、いくつかの追加の問題が発生しました.
1.これにより、TestSuiteの作成が安全でなく、異常が発生する可能性があります.test caseが実行されている場合にのみ異常が発生することを望んでいます.またTestSuiteの作成速度も影響を受ける可能性があります.
2.TestCaseが1回しか実行されないことは保証できません.たとえばRepeatedTestは1つのTestCaseを複数回実行します.だからTestCaseが少なくともimmutableのように見えることを保証することが重要です.
2つ目の質問:
tearDown()でverify()を呼び出すべきではありません.test caseが失敗した場合、まずこのverify()は呼び出す必要はありません.tearDown()で強制的に呼び出すべきではありません.次に、もしverify()も失敗したら(かなりの可能性があります)、後のexceptionは前のそれ(JUnit 4の中でこのバグを修理しました)、前のexceptionこそあなたが本当に必要ですね.
そして、verify()が失敗したら、後ろのother cleanupsも飛び越えられますが、それが一番大切ですね.
怒ってるって言ったでしょ?JUnitのような簡単なものは間違いないと思っていた.
しかし、final fieldを定義することを許さないで、私にすべての初期化をsetupの中に置いて、tearDownの中でtry-finallyを次々とセットさせて、これはコードの見苦しい問題ではありませんて、これはあまりにも人間性がありません!
私は考えて、やっと悟った.これは人民内部の矛盾ではなく,調和のとれない階級の衝突である.
JUnitはTestNGとは異なり、テストの隔離性を極度に強調しているため、テストクラスで役立つ情報(parseの良いxmlデータなど)を共有することを禁止し、test methodごとに個別のinstanceを使用します.しかし、このような代価を交換したのは意外にも1つのやらない局面です:私たちはやはり1つのinstanceが1回しか使われないと仮定することはできません.2つの異なるtest methodで使用されることはありませんが、1つのtest methodで繰り返し使用することができます.はあ、はあ、はあ.まさか?
JUnit 4になってtearDownの問題が解決しました.各@After関数でそれぞれのリソースを解放でき、フレームワークが実行されることを保証します.ただし、@Afterの関数でverify()を呼び出すことはできません.verify()の場合、テスト自体が失敗したときに呼び出すことを望んでいないからです.
構造関数の問題については、何の改善もありません.同様に、JUnit 4も広範な人民大衆のデータ共有に対する声を直視していない.TestSetupも@BeforeClassもevil static fieldを使うように要求されています.
我慢できなくて、私はまた怒った.そこで自分でAjooTestSuiteを書いて、こっそりTestNGから私がずっと食いしん坊だったannotation:@BeforeTest,@AfterTest,@BeforeSuite,@AfterSuite,@ExceptionExpectedをいくつか手に入れました.また自分で@Verifyと@Sharedを2つ記入しました.前者はテストに問題がない場合に共通のverify、例えばverify mock objectを作るために使用されます.後者はtest case間でデータを共有するために使用されます.AjooTestSuiteを使用するテストクラスは、次のように書くことができます.

public class SomeTest extends TestCase {
  public static Test suite() {
    return new AjooTestSuite(SomeTest.class);
  }
  
  //  。 final initializer !
  private final IMocksControl control = EasyMock.createStrictControl();
  private final Connection conn = control.createMock(Connection.class);

  @Verify
  public void verifyMocks() {
    control.verify();
  }

  @Shared
  public static XmlObject provideXml() {
    // ... read xml file and parse.
    return xmlObj;
  }
  
  private final XmlObj;

  // the shared xmlObj will be injected for each test case.
  public SomeTest(String name, XmlObject obj) {
    super(name);
    this.xmlOj = obj;
  }

  @BeforeSuite
  public static void initialize() {
    // some suite level initialization. No more TestSetup!
  }
  @AfterSuite
  public static void deinitialize() {
    // suite level deinitialization.
  }
  @BeforeTest
  public void setupMocks() {
    expect(conn.createStatement()).andReturn(null);
  }
  @ExceptionExpected(NullPointerException.class)
  public void test1() {
    throw new NullPointerException();
  }
}

書き終わったら、私は得意ですね.ついにJUnitの糞のような制限に耐えなくなった.コンストラクション関数はテスト実行時にのみ呼び出されます.言い換えれば、今回のtest caseは絶対immutableで、絶対スレッドは安全です.えっ!
そのほか、流行のannotationも加わった.@BeforeTest,@AfterTest,@BeforeSuite,@AfterSuite,@ExceptionExpectedはもちろん、TestNGと同じ意味です.@Verifyはverify mockのようなことをするために使われています.@Shared寸法の静的関数はtest suiteが実行されるたびに呼び出され、返されたデータは保存され、各test case instanceに注射されます.
完璧ですね.完璧です.これでTestNGに移行する必要もなく、ツール統合の問題に耐えてJUnit 4にアップグレードする必要もありません.新しいTestSuiteで、100%後方互換性があります.
しかし、事実は私の言うことが早すぎることを証明しています.私のjava専門家の同僚はまた私に冷たい水をかけてくれた.
今、あなたはEclipse/intellijの中で実行して、大丈夫で、それはsuite()関数を探して、それからあなたのカスタムtest suiteを使うことを知っています.しかし、test 1のような別のtest caseを注文して「run」と言います.何が起こるの?へへへ、それはもうあなたのsuite()関数を探しに行かないで、ああ、ああ、ああ(反響がだんだん消えていきます).それは直接あなたのクラスの中に行ってこの関数を探して呼び出しました.Surprise!
なんてことだ.Eclipse、suite()を呼び出してsuite()の中で探しませんか?
よく考えてみると、IDEのせいではありません.suite()を呼び出してもどうですか?TestSuiteは一つもありません.getTest(String name)のAPIを呼び出しましょう.実際、JUnitのTestCaseもTestごとにidがあることを強制的に保証していない.
そこで、私の3日間の仕事は無駄になりました.うううう!
要するに、とにかく、千言万語、歯を食いしばって一言にまとめる:JUnit Sucks!