Leitfaden zu JUnit 4-Regeln

1. Übersicht

In diesem Tutorial werfen wir einen Blick auf die Regelfunktion der JUnit 4-Bibliothek.

Wir beginnen mit der Einführung des JUnit-Regelmodells, bevor wir uns mit den wichtigsten Grundregeln der Distribution befassen. Außerdem erfahren Sie, wie Sie unsere eigene benutzerdefinierte JUnit-Regel schreiben und verwenden.

Weitere Informationen zum Testen mit JUnit finden Sie in unserer umfassenden JUnit-Serie.

Beachten Sie, dass bei Verwendung von JUnit 5 die Regeln durch das Erweiterungsmodell ersetzt wurden.

2. Einführung in die JUnit 4-Regeln

JUnit 4-Regeln bieten einen flexiblen Mechanismus zum Verbessern von Tests, indem Code für die Ausführung eines Testfalls ausgeführt wird . In gewissem Sinne ähnelt es den Anmerkungen @Before und @After in unserer Testklasse .

Stellen wir uns vor, wir wollten während des Testaufbaus eine Verbindung zu einer externen Ressource wie einer Datenbank herstellen und die Verbindung nach Abschluss des Tests schließen. Wenn wir diese Datenbank in mehreren Tests verwenden möchten, duplizieren wir diesen Code in jedem Test.

Mithilfe einer Regel können wir alles an einem Ort isolieren und den Code problemlos aus mehreren Testklassen wiederverwenden.

3. Verwenden von JUnit 4-Regeln

Wie können wir also Regeln verwenden? Wir können JUnit 4-Regeln verwenden, indem wir die folgenden einfachen Schritte ausführen:

  • Fügen Sie unserer Testklasse ein öffentliches Feld hinzu und stellen Sie sicher, dass der Typ dieses Felds ein Subtyp der Schnittstelle org.junit.rules.TestRule ist
  • Kommentieren Sie das Feld mit der Annotation @Rule

Im nächsten Abschnitt sehen wir, welche Projektabhängigkeiten wir benötigen, um loszulegen.

4. Maven-Abhängigkeiten

Fügen wir zunächst die Projektabhängigkeiten hinzu, die wir für unsere Beispiele benötigen. Wir benötigen nur die Hauptbibliothek von JUnit 4:

 junit junit 4.12  

Wie immer können wir die neueste Version von Maven Central erhalten.

5. In der Verteilung bereitgestellte Regeln

Natürlich bietet JUnit eine Reihe nützlicher, vordefinierter Regeln als Teil der Bibliothek . Wir finden alle diese Regeln im Paket org.junit.rules .

In diesem Abschnitt sehen wir einige Beispiele für deren Verwendung.

5.1. Die TemporaryFolder- Regel

Beim Testen benötigen wir häufig Zugriff auf eine temporäre Datei oder einen temporären Ordner. Das Verwalten des Erstellens und Löschens dieser Dateien kann jedoch umständlich sein. Mit der TemporaryFolder- Regel können wir die Erstellung von Dateien und Ordnern verwalten, die beim Beenden der Testmethode gelöscht werden sollen :

@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()); }

Wie wir sehen können, definieren wir zuerst die TemporaryFolder- Regel tmpFolder . Als Nächstes erstellt unsere Testmethode eine Datei mit dem Namen test-file.txt im temporären Ordner. Wir überprüfen dann, ob die Datei erstellt wurde und dort existiert, wo sie sollte. Wirklich schön und einfach!

Nach Abschluss des Tests sollten der temporäre Ordner und die Datei gelöscht werden. Diese Regel prüft jedoch nicht, ob das Löschen erfolgreich ist oder nicht.

Es gibt auch einige andere interessante Methoden, die in dieser Klasse erwähnenswert sind:

  • newFile()

    Wenn wir keinen Dateinamen angeben, erstellt diese Methode eine zufällig benannte neue Datei.

  • newFolder(String... folderNames)

    Um rekursiv tiefe temporäre Ordner zu erstellen, können wir diese Methode verwenden.

  • newFolder()

    Ebenso erstellt die newFolder () -Methode einen zufällig benannten neuen Ordner.

Eine erwähnenswerte Ergänzung ist, dass die TemporaryFolder- Regel ab Version 4.13 die Überprüfung gelöschter Ressourcen ermöglicht:

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

Wenn eine Ressource nicht gelöscht werden kann, schlägt der Test mit einem AssertionError fehl .

Schließlich können wir in JUnit 5 dieselbe Funktionalität mit der temporären Verzeichniserweiterung erreichen.

5.2. Die ExpectedException- Regel

Wie der Name schon sagt, können wir die ExpectedException- Regel verwenden, um zu überprüfen, ob ein Code eine erwartete Ausnahme auslöst:

@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()); }

Wie wir im obigen Beispiel sehen können, deklarieren wir zuerst die ExpectedException- Regel. Dann behaupten wir in unserem Test, dass eine IllegalArgumentException ausgelöst wird.

Mit dieser Regel können wir auch einige andere Eigenschaften der Ausnahme überprüfen, z. B. die Nachricht und die Ursache.

Eine ausführliche Anleitung zum Testen von Ausnahmen mit JUnit finden Sie in unserer hervorragenden Anleitung zum Aktivieren einer Ausnahme.

5.3. Die TestName- Regel

Put simply, the TestName rule provides the current test name inside a given test method:

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

In this trivial example, when we run the unit test, we should see the test name in the output:

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

5.4. The Timeout Rule

In this next example, we'll take a look at the Timeout rule. This rule offers a useful alternative to using the timeout parameter on an individual Test annotation.

Now, let's see how to use this rule to set a global timeout on all the test methods in our test class:

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

In the above trivial example, we first define a global timeout for all test methods of 10 seconds. Then we deliberately define a test which will take longer than 10 seconds.

When we run this test, we should see a test failure:

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"));

Im obigen Beispiel erstellen wir eine Kette von drei Regeln, die einfach die an jeden MessageLogger- Konstruktor übergebene Nachricht ausdrucken .

Wenn wir unseren Test ausführen, werden wir sehen, wie die Kette in der folgenden Reihenfolge angewendet wird:

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

9. Fazit

Zusammenfassend haben wir in diesem Tutorial die JUnit 4-Regeln im Detail untersucht.

Zuerst haben wir erklärt, was Regeln sind und wie wir sie verwenden können. Als nächstes haben wir uns eingehend mit den Regeln befasst, die im Rahmen der JUnit-Distribution enthalten sind.

Schließlich haben wir uns angesehen, wie wir unsere eigenen benutzerdefinierten Regeln definieren und Regeln miteinander verketten können.

Wie immer ist der vollständige Quellcode des Artikels auf GitHub verfügbar.