Pertanyaan Bagaimana Anda menegaskan bahwa pengecualian tertentu dilemparkan dalam tes JUnit 4?


Bagaimana saya bisa menggunakan JUnit4 idiomatis untuk menguji bahwa beberapa kode melempar pengecualian?

Sementara saya pasti bisa melakukan sesuatu seperti ini:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  boolean thrown = false;

  try {
    foo.doStuff();
  } catch (IndexOutOfBoundsException e) {
    thrown = true;
  }

  assertTrue(thrown);
}

Saya ingat bahwa ada anotasi atau Assert.xyz atau sesuatu itu jauh lebih sedikit kludgy dan jauh lebih dalam semangat JUnit untuk situasi semacam ini.


1621
2017-10-01 06:56


asal


Jawaban:


JUnit 4 memiliki dukungan untuk ini:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

Referensi: https://junit.org/junit4/faq.html#atests_7


1942
2017-10-01 07:12



Edit Sekarang JUnit5 telah dirilis, pilihan terbaik akan digunakan Assertions.assertThrows() (Lihat jawaban saya yang lain).

Jika Anda belum bermigrasi ke JUnit 5, tetapi dapat menggunakan JUnit 4.7, Anda dapat menggunakan ExpectedException Aturan:

public class FooTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    exception.expect(IndexOutOfBoundsException.class);
    foo.doStuff();
  }
}

Ini jauh lebih baik daripada @Test(expected=IndexOutOfBoundsException.class) karena tes akan gagal jika IndexOutOfBoundsException dilemparkan sebelumnya foo.doStuff()

Lihat artikel ini untuk detailnya


1165
2018-05-29 17:16



Hati-hati menggunakan pengecualian yang diharapkan, karena hanya menegaskan bahwa metode melemparkan pengecualian itu, bukan baris kode tertentu dalam ujian.

Saya cenderung menggunakan ini untuk menguji validasi parameter, karena metode tersebut biasanya sangat sederhana, tetapi tes yang lebih kompleks mungkin lebih baik dilayani dengan:

try {
    methodThatShouldThrow();
    fail( "My method didn't throw when I expected it to" );
} catch (MyException expectedException) {
}

Terapkan penilaian.


408
2017-10-01 09:31



Seperti yang dijawab sebelumnya, ada banyak cara untuk menangani pengecualian di JUnit. Tetapi dengan Java 8 ada yang lain: menggunakan Lambda Expressions. Dengan Lambda Expressions kita dapat mencapai sintaks seperti ini:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .hasNoCause();
}

assertThrown menerima antarmuka fungsional, yang contohnya dapat dibuat dengan ekspresi lambda, referensi metode, atau referensi konstruktor. assertThrown menerima bahwa antarmuka akan mengharapkan dan siap untuk menangani pengecualian.

Ini adalah teknik yang relatif sederhana namun kuat.

Lihat posting blog ini yang menjelaskan teknik ini: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

Kode sumber dapat ditemukan di sini: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

Pengungkapan: Saya adalah penulis blog dan proyek.


187
2017-07-07 22:35



di junit, ada empat cara untuk menguji pengecualian.

  • untuk junit4.x, gunakan atribut 'perkiraan' opsional dari uji annonation

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
    
  • untuk junit4.x, gunakan aturan ExpectedException

    public class XxxTest {
        @Rule
        public ExpectedException thrown = ExpectedException.none();
    
        @Test
        public void testFooThrowsIndexOutOfBoundsException() {
            thrown.expect(IndexOutOfBoundsException.class)
            //you can test the exception message like
            thrown.expectMessage("expected messages");
            foo.doStuff();
        }
    }
    
  • Anda juga dapat menggunakan cara try / catch klasik yang banyak digunakan di bawah kerangka junit 3

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        try {
            foo.doStuff();
            fail("expected exception was not occured.");
        } catch(IndexOutOfBoundsException e) {
            //if execution reaches here, 
            //it indicates this exception was occured.
            //so we need not handle it.
        }
    }
    
  • akhirnya, untuk junit5.x, Anda juga dapat menggunakan assertThrows sebagai berikut

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
        assertEquals("expected messages", exception.getMessage());
    }
    
  • begitu

    • cara 1 digunakan ketika Anda hanya ingin menguji jenis pengecualian
    • tiga cara lainnya digunakan ketika Anda ingin pesan uji pengecualian lebih lanjut
    • jika Anda menggunakan junit 3, maka yang ketiga lebih disukai
    • jika Anda suka junit 5, maka Anda harus menyukai yang ke-4
  • untuk info lebih lanjut, Anda dapat membaca dokumen ini dan panduan pengguna junit5 untuk detailnya.


84
2017-08-05 08:05



tl; dr

  • pra-JDK8: Saya akan merekomendasikan yang lama try-catch blok.

  • post-JDK8: Gunakan AssertJ atau lambdas khusus untuk menegaskan luar biasa tingkah laku.

Terlepas dari Junit 4 atau JUnit 5.

ceritanya panjang

Anda bisa menulis sendiri a lakukan sendiri  try-catch memblokir atau menggunakan alat JUnit (@Test(expected = ...) atau @Rule ExpectedException Fitur aturan JUnit).

Tetapi cara ini tidak begitu elegan dan tidak bercampur dengan baik mudah dibaca bijaksana dengan alat lain.

  1. Itu try-catch memblokir Anda harus menulis blok di sekitar perilaku yang diuji, dan menulis pernyataan di blok tangkap, yang mungkin baik-baik saja tetapi banyak yang menemukan bahwa gaya ini mengganggu aliran pembacaan tes. Anda juga perlu menulis Assert.fail pada akhir try memblokir jika tidak tes mungkin kehilangan satu sisi pernyataan; PMD, findbugs atau Sonar akan melihat masalah seperti itu.

  2. Itu @Test(expected = ...) Fitur ini menarik karena Anda dapat menulis lebih sedikit kode dan kemudian menulis tes ini dianggap kurang rentan terhadap kesalahan pengkodean. Tapi Pendekatan ini tidak memiliki beberapa area.

    • Jika tes perlu memeriksa hal-hal tambahan pada pengecualian seperti penyebab atau pesan (pesan pengecualian yang baik benar-benar penting, memiliki jenis pengecualian yang tepat mungkin tidak cukup).
    • Juga karena harapan ditempatkan di dalam metode, tergantung pada bagaimana kode yang diuji ditulis maka bagian yang salah dari kode tes dapat melemparkan pengecualian, yang mengarah ke tes positif palsu dan saya tidak yakin bahwa PMD, findbugs atau Sonar akan memberikan petunjuk tentang kode semacam itu.

      @Test(expected = WantedException.class)
      public void call2_should_throw_a_WantedException__not_call1() {
          // init tested
          tested.call1(); // may throw a WantedException
      
          // call to be actually tested
          tested.call2(); // the call that is supposed to raise an exception
      }
      
  3. Itu ExpectedException aturan juga merupakan upaya untuk memperbaiki peringatan sebelumnya, tetapi rasanya agak canggung untuk digunakan karena menggunakan gaya harapan, EasyMock pengguna tahu betul gaya ini. Mungkin nyaman bagi sebagian orang, tetapi jika Anda mengikuti Pengembangan Berbasis Perilaku (BDD) atau Atur Act Assert (AAA) menganut prinsip ExpectedException Aturan tidak akan cocok dengan gaya penulisan tersebut. Selain itu mungkin menderita masalah yang sama seperti yang @Test caranya, tergantung di mana Anda menempatkan harapan.

    @Rule ExpectedException thrown = ExpectedException.none()
    
    @Test
    public void call2_should_throw_a_WantedException__not_call1() {
        // expectations
        thrown.expect(WantedException.class);
        thrown.expectMessage("boom");
    
        // init tested
        tested.call1(); // may throw a WantedException
    
        // call to be actually tested
        tested.call2(); // the call that is supposed to raise an exception
    }
    

    Bahkan pengecualian yang diharapkan ditempatkan sebelum pernyataan tes, melanggar aliran pembacaan Anda jika tes mengikuti BDD atau AAA.

    Juga lihat ini komentar masalah pada JUnit dari penulis ExpectedException.

Jadi, opsi-opsi di atas memiliki semua muatan peringatan, dan jelas tidak kebal terhadap kesalahan pembuat kode.

  1. Ada proyek yang saya sadari setelah membuat jawaban ini yang terlihat menjanjikan, itu pengecualian tangkapan.

    Sebagaimana uraian proyek mengatakan, ia membiarkan seorang koder menulis dalam baris kode yang fasih menangkap pengecualian dan menawarkan pengecualian ini untuk pernyataan selanjutnya. Dan Anda dapat menggunakan pustaka pernyataan seperti apa Hamcrest atau AssertJ.

    Contoh cepat yang diambil dari halaman beranda:

    // given: an empty list
    List myList = new ArrayList();
    
    // when: we try to get the first element of the list
    when(myList).get(1);
    
    // then: we expect an IndexOutOfBoundsException
    then(caughtException())
            .isInstanceOf(IndexOutOfBoundsException.class)
            .hasMessage("Index: 1, Size: 0") 
            .hasNoCause();
    

    Seperti yang Anda lihat kode sangat mudah, Anda menangkap pengecualian pada baris tertentu, yang then API adalah alias yang akan menggunakan API AssertJ (mirip dengan menggunakan assertThat(ex).hasNoCause()...). Pada titik tertentu, proyek ini bergantung pada FEST-Assert leluhur AssertJ. EDIT: Tampaknya proyek ini menyeduh dukungan Lambdas Java 8.

    Saat ini perpustakaan ini memiliki dua kekurangan:

    • Pada saat penulisan ini perlu dicatat bahwa perpustakaan ini didasarkan pada Mockito 1.x karena menciptakan tiruan dari objek yang diuji di belakang layar. Karena Mockito masih belum diperbarui pustaka ini tidak dapat berfungsi dengan kelas akhir atau metode akhir. Dan bahkan jika itu didasarkan pada mockito 2 dalam versi saat ini, ini akan mengharuskan untuk menyatakan pembuat tiruan global (inline-mock-maker), sesuatu yang mungkin tidak seperti yang Anda inginkan, karena pembuat mock ini memiliki kekurangan yang berbeda dari pembuat mock reguler.

    • Itu membutuhkan lagi ketergantungan uji lainnya.

    Masalah-masalah ini tidak akan berlaku begitu perpustakaan akan mendukung lambdas, namun fungsinya akan diduplikasi oleh AssertJ toolset.

    Mempertimbangkan semua hal jika Anda tidak ingin menggunakan alat pengecualian tangkapan, saya akan merekomendasikan cara lama yang baik dari try-catch memblokir, setidaknya hingga JDK7. Dan untuk pengguna JDK 8 Anda mungkin lebih suka menggunakan AssertJ karena menawarkan lebih dari sekadar menegaskan pengecualian.

  2. Dengan JDK8, lambdas memasuki adegan uji, dan mereka telah terbukti menjadi cara yang menarik untuk menegaskan perilaku yang luar biasa. AssertJ telah diperbarui untuk menyediakan API lancar yang bagus untuk menegaskan perilaku yang luar biasa.

    Dan uji sampel dengan AssertJ :

    @Test
    public void test_exception_approach_1() {
        ...
        assertThatExceptionOfType(IOException.class)
                .isThrownBy(() -> someBadIOOperation())
                .withMessage("boom!"); 
    }
    
    @Test
    public void test_exception_approach_2() {
        ...
        assertThatThrownBy(() -> someBadIOOperation())
                .isInstanceOf(Exception.class)
                .hasMessageContaining("boom");
    }
    
    @Test
    public void test_exception_approach_3() {
        ...
        // when
        Throwable thrown = catchThrowable(() -> someBadIOOperation());
    
        // then
        assertThat(thrown).isInstanceOf(Exception.class)
                          .hasMessageContaining("boom");
    }
    
  3. Dengan penulisan ulang hampir lengkap dari JUnit 5, pernyataan telah ditingkatkan sedikit, mereka mungkin terbukti menarik sebagai cara keluar dari kotak untuk menegaskan dengan benar pengecualian. Tetapi benar-benar API penegasan masih sedikit buruk, tidak ada apa pun di luar assertThrows.

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }
    

    Seperti yang Anda perhatikan assertEquals masih kembali void, dan karena itu tidak memungkinkan pernyataan chaining seperti AssertJ.

    Juga jika Anda ingat nama bentrokan dengan Matcher atau Assert, bersiaplah untuk bertemu dengan bentrokan yang sama Assertions.

Saya ingin menyimpulkan bahwa hari ini (2017-03-03) AssertJkemudahan penggunaan, API yang dapat ditemukan, laju perkembangan yang cepat dan sebagai secara de facto ketergantungan uji adalah solusi terbaik dengan JDK8 terlepas dari kerangka uji (JUnit atau tidak), sebelumnya JDK sebaliknya harus bergantung pada try-catch memblokir bahkan jika mereka merasa kikuk.

Jawaban ini telah disalin dari pertanyaan lain yang tidak memiliki visibilitas yang sama, saya adalah penulis yang sama.


58
2017-12-07 14:19



BDD Solusi Gaya: JUnit 4 + Catch Exception + AssertJ

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(foo).doStuff();

    then(caughtException()).isInstanceOf(IndexOutOfBoundsException.class);

}

Kode sumber

Dependensi

eu.codearte.catch-exception:catch-exception:1.3.3

31
2017-11-15 19:17



Bagaimana dengan ini: Tangkap pengecualian yang sangat umum, pastikan itu membuatnya keluar dari blok tangkap, kemudian menegaskan bahwa kelas pengecualian adalah apa yang Anda harapkan. Pernyataan ini akan gagal jika a) pengecualian adalah tipe yang salah (mis. Jika Anda mendapatkan Null Pointer sebagai gantinya) dan b) pengecualian tidak pernah dilemparkan.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

  try {
    foo.doStuff();
  } catch (Throwable ex) {
    e = ex;
  }

  assertTrue(e instanceof IndexOutOfBoundsException);
}

30
2017-10-01 07:03



Menggunakan sebuah AssertJ pernyataan, yang dapat digunakan bersama JUnit:

import static org.assertj.core.api.Assertions.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  Foo foo = new Foo();

  assertThatThrownBy(() -> foo.doStuff())
        .isInstanceOf(IndexOutOfBoundsException.class);
}

Lebih baik daripada @Test(expected=IndexOutOfBoundsException.class)karena menjamin garis yang diharapkan dalam tes melemparkan pengecualian dan memungkinkan Anda memeriksa detail lebih lanjut tentang pengecualian, seperti pesan, lebih mudah:

assertThatThrownBy(() ->
       {
         throw new Exception("boom!");
       })
    .isInstanceOf(Exception.class)
    .hasMessageContaining("boom");

Instruksi Maven / Gradle di sini.


30
2018-03-05 11:07



Untuk mengatasi masalah yang sama, saya membuat proyek kecil: http://code.google.com/p/catch-exception/

Dengan menggunakan pembantu kecil ini, Anda akan menulis

verifyException(foo, IndexOutOfBoundsException.class).doStuff();

Ini kurang verbose daripada aturan ExpectedException dari JUnit 4.7. Dibandingkan dengan solusi yang disediakan oleh skaffman, Anda dapat menentukan di baris kode mana Anda mengharapkan pengecualian. Saya harap ini membantu.


29
2017-10-28 09:26