Ръководство за правила за JUnit 4

1. Общ преглед

В този урок ще разгледаме функцията Правила, предоставена от библиотеката JUnit 4.

Ще започнем с въвеждането на JUnit Rules Model, преди да преминем през най-важните базови правила, предоставени от дистрибуцията. Освен това ще видим и как да напишем и използваме нашето собствено правило JUnit.

За да научите повече за тестването с JUnit, разгледайте нашата изчерпателна серия JUnit.

Имайте предвид, че ако използвате JUnit 5, правилата са заменени от модела Extension.

2. Въведение в правилата на JUnit 4

Правилата на JUnit 4 предоставят гъвкав механизъм за подобряване на тестовете чрез стартиране на някакъв код около изпълнението на тестовия случай . В известен смисъл, това е подобно на като @Before и @After анотации в нашия тест клас.

Нека си представим, че сме искали да се свържем с външен ресурс като база данни по време на настройката на теста и след това да затворим връзката след приключване на теста. Ако искаме да използваме тази база данни в множество тестове, в крайна сметка ще дублираме този код във всеки тест.

Използвайки правило, можем да изолираме всичко на едно място и да използваме повторно кода лесно от множество тестови класове.

3. Използване на правила JUnit 4

И така, как можем да използваме правила? Можем да използваме правилата на JUnit 4, като следваме тези прости стъпки:

  • Добавете публично поле към нашия тестов клас и се уверете, че типът на това поле е подтип на интерфейса org.junit.rules.TestRule
  • Коментирайте полето с анотацията @Rule

В следващия раздел ще видим какви проектни зависимости са ни необходими, за да започнем.

4. Зависимости на Maven

Първо, нека добавим зависимостите от проекта, които ще са ни необходими за нашите примери. Ще ни трябва само основната библиотека на JUnit 4:

 junit junit 4.12  

Както винаги, можем да получим най-новата версия от Maven Central.

5. Правила, предоставени в дистрибуцията

Разбира се, JUnit предоставя редица полезни, предварително дефинирани правила като част от библиотеката . Всички тези правила можем да намерим в пакета org.junit.rules .

В този раздел ще видим няколко примера за това как да ги използваме.

5.1. В TemporaryFolder Правилото

При тестване често се нуждаем от достъп до временен файл или папка. Управлението на създаването и изтриването на тези файлове обаче може да бъде тромаво. Използвайки правилото TemporaryFolder , можем да управляваме създаването на файлове и папки, които трябва да бъдат изтрити при прекратяване на тестовия метод :

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

Както виждаме, първо определяме правилото TemporaryFolder tmpFolder . След това нашият метод за тестване създава файл, наречен test-file.txt във временната папка. След това проверяваме дали файлът е създаден и съществува ли там, където трябва. Наистина хубаво и просто!

Когато тестът приключи, временната папка и файл трябва да бъдат изтрити. Това правило обаче не проверява дали изтриването е успешно или не.

Има и няколко други интересни метода, които си струва да се споменат в този клас:

  • newFile()

    Ако не предоставим никакво име на файл, тогава този метод създава произволно наречен нов файл.

  • newFolder(String... folderNames)

    За да създадем рекурсивно дълбоки временни папки, можем да използваме този метод.

  • newFolder()

    По същия начин методът newFolder () създава произволно наречена нова папка.

Хубаво допълнение, което си струва да се спомене, е, че като се започне с версия 4.13, правилото TemporaryFolder позволява проверка на изтрити ресурси:

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

Ако ресурс не може да бъде изтрит, тестът с неуспех с AssertionError .

И накрая, в JUnit 5 можем да постигнем същата функционалност, използвайки разширението Временна директория.

5.2. В ExpectedException Правилото

Както подсказва името, можем да използваме правилото ExpectedException, за да проверим дали някои кодове хвърлят очаквано изключение:

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

Както виждаме в горния пример, първо декларираме правилото ExpectedException . След това, в нашия тест, ние твърдим, че е хвърлено IllegalArgumentException .

Използвайки това правило, можем да проверим и някои други свойства на изключението, като съобщението и причината.

За задълбочено ръководство за тестване на изключения с JUnit, вижте нашето отлично ръководство за това как да заявите изключение.

5.3. В TestName Правилото

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

В горния пример създаваме верига от три правила, които просто разпечатат съобщението, предадено на всеки конструктор MessageLogger .

Когато стартираме нашия тест, ще видим как се прилага веригата в ред:

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

9. Заключение

За да обобщим, в този урок разгледахме подробно правилата на JUnit 4.

Първо започнахме, като обяснихме какво представляват правилата и как можем да ги използваме. След това разгледахме задълбочено правилата, които идват като част от разпределението на JUnit.

Накрая разгледахме как можем да дефинираме нашето собствено правило и как да свържем правилата заедно.

Както винаги, пълният изходен код на статията е достъпен в GitHub.