Apa pesanan Junit @ Sebelum / @ Setelah dipanggil?

133

Saya memiliki Suite Tes Integrasi. Saya memiliki IntegrationTestBasekelas untuk memperpanjang semua tes saya. Kelas dasar ini memiliki metode @Before( public void setUp()) dan @After( public void tearDown()) untuk membangun koneksi API dan DB. Apa yang saya lakukan hanyalah mengganti kedua metode di setiap testcase dan menelepon super.setUp()dan super.tearDown(). Namun ini dapat menyebabkan masalah jika seseorang lupa untuk memanggil super atau menempatkan mereka di tempat yang salah dan pengecualian dilemparkan dan mereka lupa memanggil super pada akhirnya atau sesuatu.

Yang ingin saya lakukan adalah membuat setUpdan tearDownmetode pada kelas dasar finaldan kemudian hanya menambahkan metode @Beforedan penjelasan kita sendiri @After. Melakukan beberapa tes awal tampaknya selalu memanggil dalam urutan ini:

Base @Before
Test @Before
Test
Test @After
Base @After

tapi saya hanya sedikit khawatir bahwa pesanan tidak dijamin dan itu bisa menyebabkan masalah. Saya melihat sekeliling dan belum melihat apa-apa tentang masalah ini. Adakah yang tahu kalau saya bisa melakukan itu dan tidak ada masalah?

Kode:

public class IntegrationTestBase {

    @Before
    public final void setUp() { *always called 1st?* }

    @After
    public final void tearDown() { *always called last?* }
}


public class MyTest extends IntegrationTestBase {

    @Before
    public final void before() { *always called 2nd?* }

    @Test
    public void test() { *always called 3rd?* }

    @After
    public final void after() { *always called 4th?* }
}
Joel
sumber
1
Apakah yang MyTesthilang itu extends?
aioobe
@aioobe: tidak lagi :)
Joel

Jawaban:

135

Ya, perilaku ini dijamin:

@Before:

The @Beforemetode superclasses akan dijalankan sebelum orang-orang dari kelas saat ini, kecuali mereka ditimpa di kelas saat ini. Tidak ada pemesanan lain yang ditentukan.

@After:

The @Aftermetode dideklarasikan di superclasses akan dijalankan setelah orang-orang dari kelas saat ini, kecuali mereka ditimpa di kelas saat ini.

axtavt
sumber
15
Agar jelas, urutan eksekusi semua @Beforemetode tidak dijamin. Jika ada 10 @Beforemetode, masing-masing dapat dieksekusi dalam urutan apa pun; sebelum metode lainnya.
Swati
5
Jadi, alih-alih mengutip dokumentasi yang agak ambigu, bisakah Anda menjelaskannya dengan kata-kata Anda sendiri? Apakah @Beforedan @Aftermetode dijalankan sebelum setiap metode kelas lainnya (sekali per metode), atau tepat sebelum dan setelah seluruh rangkaian metode kelas (satu kali per kelas)?
BT
5
Lihat hasil tangkapan penting yang dinyatakan oleh John Q Citizen: "ini hanya berlaku jika setiap metode yang ditandai dengan @Sebelum memiliki nama unik dalam hierarki kelas" Sangat penting untuk diingat!
Bruno Bossola
Saya mengalami konflik nama menggunakan nama metode yang sama pada metode @Before (d) di kelas dan metode lain di kelas supernya, pada junit-4.12.
Stephane
Apakah aturan ini juga berlaku untuk metode ConcordeRunner @BeforeExample?
Adrian Pronk
51

Satu gotcha potensial yang telah menggigit saya sebelumnya:

Saya ingin memiliki paling banyak satu @Beforemetode di setiap kelas tes, karena urutan menjalankan @Beforemetode yang ditentukan dalam kelas tidak dijamin. Biasanya, saya akan memanggil metode seperti itu setUpTest().

Tetapi, meskipun @Beforedidokumentasikan sebagai The @Before methods of superclasses will be run before those of the current class. No other ordering is defined., ini hanya berlaku jika setiap metode yang ditandai dengan @Beforememiliki nama unik dalam hirarki kelas.

Sebagai contoh, saya memiliki yang berikut ini:

public class AbstractFooTest {
  @Before
  public void setUpTest() { 
     ... 
  }
}

public void FooTest extends AbstractFooTest {
  @Before
  public void setUpTest() { 
    ...
  }
}

Saya berharap AbstractFooTest.setUpTest()untuk berlari sebelumnya FooTest.setUpTest(), tetapi hanya FooTest.setupTest()dieksekusi. AbstractFooTest.setUpTest()tidak dipanggil sama sekali.

Kode harus dimodifikasi sebagai berikut agar berfungsi:

public void FooTest extends AbstractFooTest {
  @Before
  public void setUpTest() {
    super.setUpTest();
    ...
  }
}
John Q Citizen
sumber
Mengapa tidak mengubah nama metode @Before di kelas dasar? Ini akan menyelamatkan Anda dari keharusan menelepon ke super di semua anak ... lagipula tangkapan yang bagus dengan masalah nama yang sama
Lawrence Tierney
24
Hanya sebuah komentar untuk membuat semuanya lebih aman: Untuk menghindari bentrokan nama, Anda dapat membuat @Before/ @Aftermetode di kelas dasar final, sehingga kompiler akan mengeluh jika Anda (secara tidak sengaja) mencoba menimpa mereka di subkelas.
Stefan Winkler
4
Metode induk dengan nama yang sama tidak dijalankan tidak terdengar seperti perilaku JUnit. Kedengarannya seperti cara kerja over-riding dasar di OOP. Metode induk pada dasarnya tidak ada saat dijalankan. Anak itu menggantinya untuk semua maksud dan tujuan. Begitulah cara kerja Java.
Brandon
1
Gotcha lainnya adalah bahwa kelas induk harus bersifat publik, jika tidak, @Beforemetode yang ditandai akan diabaikan jika subkelas juga memiliki @Beforemetode.
rusins
21

Saya pikir berdasarkan pada dokumentasi @Beforedan @Afterkesimpulan yang tepat adalah memberikan metode nama yang unik. Saya menggunakan pola berikut dalam pengujian saya:

public abstract class AbstractBaseTest {

  @Before
  public final void baseSetUp() { // or any other meaningful name
    System.out.println("AbstractBaseTest.setUp");
  }

  @After
  public final void baseTearDown() { // or any other meaningful name
    System.out.println("AbstractBaseTest.tearDown");
  }
}

dan

public class Test extends AbstractBaseTest {

  @Before
  public void setUp() {
    System.out.println("Test.setUp");
  }

  @After
  public void tearDown() {
    System.out.println("Test.tearDown");
  }

  @Test
  public void test1() throws Exception {
    System.out.println("test1");
  }

  @Test
  public void test2() throws Exception {
    System.out.println("test2");
  }
}

berikan sebagai hasilnya

AbstractBaseTest.setUp
Test.setUp
test1
Test.tearDown
AbstractBaseTest.tearDown
AbstractBaseTest.setUp
Test.setUp
test2
Test.tearDown
AbstractBaseTest.tearDown

Keuntungan dari pendekatan ini: Pengguna kelas AbstractBaseTest tidak dapat mengganti metode setUp / tearDown secara tidak sengaja. Jika mereka mau, mereka perlu tahu nama persisnya dan bisa melakukannya.

(Kecil) Kerugian dari pendekatan ini: Pengguna tidak dapat melihat bahwa ada hal-hal yang terjadi sebelum atau setelah setUp / tearDown mereka. Mereka perlu tahu bahwa hal-hal ini disediakan oleh kelas abstrak. Tapi saya berasumsi itulah alasan mengapa mereka menggunakan kelas abstrak

Matthias Hoefel
sumber
2
contoh yang bagus - akan lebih ilustratif jika Anda memiliki dua metode @Test, sehingga dapat dilihat bahwa setUp dan tearDown membungkus setiap metode pengujian.
Tandai
Saya pikir ini adalah dasar untuk jawaban terbaik untuk OP, tetapi Anda harus mengisi jawaban Anda untuk standalone. Bisakah Anda menambah contoh Anda untuk mencakup alternatif yang disarankan orang lain, dan menjelaskan mengapa proposal Anda lebih unggul?
wachr
2

Jika Anda membalikkan keadaan, Anda bisa mendeklarasikan abstrak kelas dasar Anda, dan memiliki keturunan yang menyatakan metode setUp dan tearDown (tanpa anotasi) yang dipanggil dalam metode setUp and tearDown kelas dasar '.

Buhb
sumber
1
bukan ide yang buruk, tapi saya tidak ingin memaksakan kontrak pada tes yang tidak memerlukan setup / tearDown mereka sendiri
Joel
2

Anda dapat menggunakan @BeforeClassanotasi untuk memastikan yang setup()selalu disebut pertama. Demikian pula, Anda dapat menggunakan @AfterClassanotasi untuk memastikan yang tearDown()selalu disebut terakhir.

Ini biasanya tidak disarankan, tetapi didukung .

Ini bukan apa yang Anda inginkan - tetapi pada dasarnya itu akan membuat koneksi DB Anda terbuka sepanjang waktu tes Anda berjalan, dan kemudian tutup sekali dan untuk semua di akhir.

Swati
sumber
2
Sebenarnya, jika Anda melakukan hal ini, saya akan merekomendasikan menciptakan metode setupDB()dan closeDB()dan menandai mereka dengan @BeforeClassdan @AfterClassdan mengganti / sebelum Anda setelah metode dengan setup()dantearDown()
Swati
Metode dijelaskan dengan @BeforeClassdan @AfterClassharus statis. Bagaimana dengan kasus ini, ketika kita ingin menggunakan variabel instan di dalam metode ini?
Pratik Singhal
Peringatan saat menggunakan @BeforeClassdengan Powermock: ini hanya berfungsi untuk percobaan pertama. Lihat masalah ini: github.com/powermock/powermock/issues/398
Dagmar
2

Ini bukan jawaban untuk pertanyaan tagline, tetapi ini adalah jawaban untuk masalah yang disebutkan dalam tubuh pertanyaan. Alih-alih menggunakan @Before atau @After, lihatlah menggunakan @ org.junit.Rule karena itu memberi Anda lebih banyak fleksibilitas. ExternalResource (per 4.7) adalah aturan yang Anda akan paling tertarik jika Anda mengelola koneksi. Juga, Jika Anda ingin jaminan pelaksanaan aturan Anda gunakan RuleChain (per 4.10). Saya percaya semua ini tersedia saat pertanyaan ini diajukan. Contoh kode di bawah ini disalin dari javadocs ExternalResource.

 public static class UsesExternalResource {
  Server myServer= new Server();

  @Rule
  public ExternalResource resource= new ExternalResource() {
      @Override
      protected void before() throws Throwable {
          myServer.connect();
         };

      @Override
      protected void after() {
          myServer.disconnect();
         };
     };

  @Test
  public void testFoo() {
      new Client().run(myServer);
     }
 }
sukseshawk
sumber