Panduan untuk Aturan JUnit 4

1. Ikhtisar

Dalam tutorial ini, kita akan melihat fitur Rules yang disediakan oleh library JUnit 4.

Kami akan mulai dengan memperkenalkan Model Aturan JUnit sebelum membahas aturan dasar terpenting yang disediakan oleh distribusi. Selain itu, kita juga akan melihat cara menulis dan menggunakan Aturan JUnit kustom kita sendiri.

Untuk mempelajari lebih lanjut tentang pengujian dengan JUnit, lihat seri JUnit kami yang komprehensif.

Perhatikan bahwa jika Anda menggunakan JUnit 5, aturan telah diganti oleh model Ekstensi.

2. Pengantar Aturan JUnit 4

Aturan JUnit 4 menyediakan mekanisme yang fleksibel untuk menyempurnakan pengujian dengan menjalankan beberapa kode di sekitar eksekusi kasus pengujian . Dalam hal tertentu, itu mirip dengan memiliki @Before dan @After penjelasan di kelas pengujian kami.

Bayangkan kita ingin menghubungkan ke sumber daya eksternal seperti database selama pengaturan pengujian dan kemudian menutup koneksi setelah pengujian selesai. Jika kami ingin menggunakan database itu dalam beberapa pengujian, kami akan menggandakan kode itu di setiap pengujian.

Dengan menggunakan aturan, kita dapat memisahkan semuanya di satu tempat dan menggunakan kembali kode dengan mudah dari beberapa kelas pengujian.

3. Menggunakan Aturan JUnit 4

Jadi bagaimana kita bisa menggunakan aturan? Kita dapat menggunakan aturan JUnit 4 dengan mengikuti langkah-langkah sederhana berikut:

  • Tambahkan bidang publik ke kelas pengujian kami dan pastikan bahwa jenis bidang ini adalah subtipe dari antarmuka org.junit.rules.TestRule
  • Beri anotasi bidang dengan anotasi @Rule

Di bagian selanjutnya, kita akan melihat dependensi proyek apa yang kita butuhkan untuk memulai.

4. Ketergantungan Maven

Pertama, mari tambahkan dependensi proyek yang kita perlukan untuk contoh kita. Kami hanya membutuhkan perpustakaan utama JUnit 4:

 junit junit 4.12  

Seperti biasa, kami bisa mendapatkan versi terbaru dari Maven Central.

5. Aturan yang Diberikan dalam Distribusi

Tentu saja, JUnit menyediakan sejumlah aturan berguna yang telah ditentukan sebelumnya sebagai bagian dari pustaka . Kita dapat menemukan semua aturan ini di paket org.junit.rules .

Di bagian ini, kita akan melihat beberapa contoh cara menggunakannya.

5.1. The TemporaryFolder Aturan

Saat menguji, kami sering membutuhkan akses ke file atau folder sementara. Namun, mengelola pembuatan dan penghapusan file ini bisa jadi merepotkan. Dengan menggunakan aturan TemporaryFolder , kita dapat mengatur pembuatan file dan folder yang harus dihapus ketika metode pengujian berakhir :

@Rule public TemporaryFolder tmpFolder = new TemporaryFolder(); @Test public void givenTempFolderRule_whenNewFile_thenFileIsCreated() throws IOException { File testFile = tmpFolder.newFile("test-file.txt"); assertTrue("The file should have been created: ", testFile.isFile()); assertEquals("Temp folder and test file should match: ", tmpFolder.getRoot(), testFile.getParentFile()); }

Seperti yang bisa kita lihat, pertama-tama kita mendefinisikan aturan TemporaryFolder tmpFolder . Selanjutnya, metode pengujian kami membuat file bernama test-file.txt di folder sementara. Kami kemudian memeriksa bahwa file telah dibuat dan ada di tempat yang seharusnya. Sangat bagus dan sederhana!

Saat pengujian selesai, folder dan file sementara harus dihapus. Namun, aturan ini tidak memeriksa apakah penghapusan berhasil atau tidak.

Ada juga beberapa metode menarik lainnya yang perlu disebutkan di kelas ini:

  • newFile()

    Jika kami tidak memberikan nama file apa pun, maka metode ini membuat file baru dengan nama acak.

  • newFolder(String... folderNames)

    Untuk membuat folder sementara yang dalam secara rekursif, kita dapat menggunakan metode ini.

  • newFolder()

    Demikian juga, metode newFolder () membuat folder baru yang diberi nama secara acak.

Tambahan yang bagus yang patut disebutkan adalah bahwa dimulai dengan versi 4.13, aturan TemporaryFolder memungkinkan verifikasi sumber daya yang dihapus:

@Rule public TemporaryFolder folder = TemporaryFolder.builder().assureDeletion().build();

Jika sumber daya tidak dapat dihapus, pengujian dengan gagal dengan AssertionError .

Akhirnya, di JUnit 5, kita dapat mencapai fungsionalitas yang sama menggunakan ekstensi Direktori Sementara.

5.2. The ExpectedException Aturan

Seperti namanya, kita bisa menggunakan aturan ExpectedException untuk memverifikasi bahwa beberapa kode menampilkan pengecualian yang diharapkan:

@Rule public final ExpectedException thrown = ExpectedException.none(); @Test public void givenIllegalArgument_whenExceptionThrown_MessageAndCauseMatches() { thrown.expect(IllegalArgumentException.class); thrown.expectCause(isA(NullPointerException.class)); thrown.expectMessage("This is illegal"); throw new IllegalArgumentException("This is illegal", new NullPointerException()); }

Seperti yang bisa kita lihat pada contoh di atas, pertama-tama kita mendeklarasikan aturan ExpectedException . Kemudian, dalam pengujian kami, kami menyatakan bahwa IllegalArgumentException dilempar.

Dengan menggunakan aturan ini, kami juga dapat memverifikasi beberapa properti pengecualian lainnya, seperti pesan dan penyebabnya.

Untuk panduan mendalam untuk menguji pengecualian dengan JUnit, lihat panduan terbaik kami tentang cara Menegaskan Pengecualian.

5.3. The TestName Aturan

Sederhananya, aturan TestName menyediakan nama pengujian saat ini di dalam metode pengujian yang diberikan:

@Rule public TestName name = new TestName(); @Test public void givenAddition_whenPrintingTestName_thenTestNameIsDisplayed() { LOG.info("Executing: {}", name.getMethodName()); assertEquals("givenAddition_whenPrintingTestName_thenTestNameIsDisplayed", name.getMethodName()); }

Dalam contoh sederhana ini, ketika kita menjalankan pengujian unit, kita akan melihat nama pengujian di keluaran:

INFO c.baeldung.rules.JUnitRulesUnitTest - Executing: givenAddition_whenPrintingTestName_thenTestNameIsDisplayed

5.4. The Timeout Aturan

Dalam contoh berikut ini, kita akan melihat aturan Timeout . Aturan ini menawarkan alternatif yang berguna untuk menggunakan parameter waktu tunggu pada anotasi Tes individu .

Sekarang, mari kita lihat cara menggunakan aturan ini untuk menyetel waktu tunggu global pada semua metode pengujian di kelas pengujian kami:

@Rule public Timeout globalTimeout = Timeout.seconds(10); @Test public void givenLongRunningTest_whenTimout_thenTestFails() throws InterruptedException { TimeUnit.SECONDS.sleep(20); }

Dalam contoh sepele di atas, pertama-tama kita menetapkan waktu tunggu global untuk semua metode pengujian 10 detik . Lalu kami sengaja menentukan tes yang akan memakan waktu lebih dari 10 detik.

Saat kami menjalankan pengujian ini, kami akan melihat pengujian gagal:

org.junit.runners.model.TestTimedOutException: test timed out after 10 seconds ...

5.5. The ErrorCollector Rule

Next up we're going to take a look at the ErrorCollector rule. This rule allows the execution of a test to continue after the first problem is found.

Let's see how we can use this rule to collect all the errors and report them all at once when the test terminates:

@Rule public final ErrorCollector errorCollector = new ErrorCollector(); @Test public void givenMultipleErrors_whenTestRuns_thenCollectorReportsErrors() { errorCollector.addError(new Throwable("First thing went wrong!")); errorCollector.addError(new Throwable("Another thing went wrong!")); errorCollector.checkThat("Hello World", not(containsString("ERROR!"))); }

In the above example, we add two errors to the collector. When we run the test, the execution continues, but the test will fail at the end.

In the output, we will see both errors reported:

java.lang.Throwable: First thing went wrong! ... java.lang.Throwable: Another thing went wrong!

5.6. The Verifier Rule

The Verifier rule is an abstract base class that we can use when we wish to verify some additional behavior from our tests. In fact, the ErrorCollector rule we saw in the last section extends this class.

Let's now take a look at a trivial example of defining our own verifier:

private List messageLog = new ArrayList(); @Rule public Verifier verifier = new Verifier() { @Override public void verify() { assertFalse("Message Log is not Empty!", messageLog.isEmpty()); } }; 

Here, we define a new Verifier and override the verify() method to add some extra verification logic. In this straightforward example, we simply check to see that the message log in our example isn't empty.

Now, when we run the unit test and add a message, we should see that our verifier has been applied:

@Test public void givenNewMessage_whenVerified_thenMessageLogNotEmpty() { // ... messageLog.add("There is a new message!"); }

5.7. The DisableOnDebug Rule

Sometimes we may want to disable a rule when we're debugging. For example, it’s often desirable to disable a Timeout rule when debugging to avoid our test timing out and failing before we've had time to debug it properly.

The DisableOnDebug Rule does precisely this and allows us to label certain rules to be disabled when debugging:

@Rule public DisableOnDebug disableTimeout = new DisableOnDebug(Timeout.seconds(30));

In the example above we can see that in order to use this rule, we simply pass the rule we want to disable to the constructor.

The main benefit of this rule is that we can disable rules without making any modifications to our test classes during debugging.

5.8. The ExternalResource Rule

Typically, when writing integration tests, we may wish to set up an external resource before a test and tear it down afterward. Thankfully, JUnit provides another handy base class for this.

We can extend the abstract class ExternalResource to set up an external resource before a test, such as a file or a database connection. In fact, the TemporaryFolder rule we saw earlier extends ExternalResource.

Let's take a quick look at how we could extend this class:

@Rule public final ExternalResource externalResource = new ExternalResource() { @Override protected void before() throws Throwable { // code to set up a specific external resource. }; @Override protected void after() { // code to tear down the external resource }; };

In this example, when we define an external resource we simply need to override the before() method and after() method in order to set up and tear down our external resource.

6. Applying Class Rules

Up until now, all the examples we've looked at have applied to single test case methods. However, sometimes we might want to apply a rule at the test class level. We can accomplish this by using the @ClassRule annotation.

This annotation works very similarly to @Rule but wraps a rule around a whole test β€” the main difference being that the field we use for our class rule must be static:

@ClassRule public static TemporaryFolder globalFolder = new TemporaryFolder();

7. Defining a Custom JUnit Rule

As we've seen, JUnit 4 provides a number of useful rules out of the box. Of course, we can define our own custom rules. To write a custom rule, we need to implement the TestRule interface.

Let's take a look at an example of defining a custom test method name logger rule:

public class TestMethodNameLogger implements TestRule { private static final Logger LOG = LoggerFactory.getLogger(TestMethodNameLogger.class); @Override public Statement apply(Statement base, Description description) { logInfo("Before test", description); try { return new Statement() { @Override public void evaluate() throws Throwable { base.evaluate(); } }; } finally { logInfo("After test", description); } } private void logInfo(String msg, Description description) { LOG.info(msg + description.getMethodName()); } }

As we can see, the TestRule interface contains one method called apply(Statement, Description) that we must override to return an instance of Statement. The statement represents our tests within the JUnit runtime. When we call the evaluate() method, this executes our test.

In this example, we log a before and after message and include from the Description object the method name of the individual test.

8. Using Rule Chains

In this final section, we'll take a look at how we can order several test rules using the RuleChain rule:

@Rule public RuleChain chain = RuleChain.outerRule(new MessageLogger("First rule")) .around(new MessageLogger("Second rule")) .around(new MessageLogger("Third rule"));

Dalam contoh di atas, kami membuat rangkaian tiga aturan yang hanya mencetak pesan yang diteruskan ke setiap konstruktor MessageLogger .

Saat kami menjalankan pengujian kami, kami akan melihat bagaimana rantai diterapkan secara berurutan:

Starting: First rule Starting: Second rule Starting: Third rule Finished: Third rule Finished: Second rule Finished: First rule

9. Kesimpulan

Untuk meringkas, dalam tutorial ini, kita telah menjelajahi aturan JUnit 4 secara mendetail.

Pertama, kami mulai dengan menjelaskan apa itu aturan dan bagaimana kami dapat menggunakannya. Selanjutnya, kami melihat secara mendalam aturan yang menjadi bagian dari distribusi JUnit.

Terakhir, kita melihat bagaimana kita bisa mendefinisikan aturan kustom kita sendiri dan bagaimana merangkai aturan bersama.

Seperti biasa, kode sumber lengkap artikel tersedia di GitHub.