Guide to JUnit 4 Rules – JUnit 4规则指南

最后修改: 2019年 8月 22日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to take a look at the Rules feature provided by the JUnit 4 library.

在本教程中,我们要看一下JUnit 4库提供的规则功能。

We’ll begin by introducing the JUnit Rules Model before walking through the most important base rules provided by the distribution. Additionally, we’ll also see how to write and use our own custom JUnit Rule.

我们将首先介绍JUnit规则模型,然后再浏览由发行版提供的最重要的基本规则。此外,我们还将看到如何编写和使用我们自己的自定义JUnit规则。

To learn more about testing with JUnit, check out our comprehensive JUnit series.

要了解有关使用JUnit测试的更多信息,请查看我们全面的JUnit系列

Note that if you’re using JUnit 5, rules have been replaced by the Extension model.

注意,如果你使用JUnit 5,规则已经被Extension模型所取代。

2. Introduction to JUnit 4 Rules

2.JUnit 4规则介绍

JUnit 4 rules provide a flexible mechanism to enhance tests by running some code around a test case execution. In some sense, it’s similar to having @Before and @After annotations in our test class.

JUnit 4规则提供了一种灵活的机制,通过在测试用例执行周围运行一些代码来增强测试。在某种意义上,它类似于在我们的测试类中拥有@Before@After注释

Let’s imagine we wanted to connect to an external resource such as a database during test setup and then close the connection after our test finishes. If we want to use that database in multiple tests, we’d end up duplicating that code in every test.

让我们想象一下,我们想在测试设置期间连接到一个外部资源,如数据库,然后在测试完成后关闭连接。如果我们想在多个测试中使用该数据库,我们最终会在每个测试中重复这些代码。

By using a rule, we can have everything isolated in one place and reuse the code easily from multiple test classes.

通过使用规则,我们可以将所有的东西都隔离在一个地方,并轻松地从多个测试类中重用代码。

3. Using JUnit 4 Rules

3.使用JUnit 4规则

So how can we use rules? We can use JUnit 4 rules by following these simple steps:

那么我们怎样才能使用规则呢?我们可以通过以下简单步骤使用JUnit 4规则。

  • Add a public field to our test class and ensure that the type of this field is a subtype of the org.junit.rules.TestRule interface
  • Annotate the field with the @Rule annotation

In the next section, we’ll see what project dependencies we need to get started.

在下一节,我们将看到我们需要哪些项目的依赖性来开始。

4. Maven Dependencies

4.Maven的依赖性

First, let’s add the project dependencies we’ll need for our examples. We’ll only need the main JUnit 4 library:

首先,让我们添加我们的例子所需的项目依赖。我们只需要主要的JUnit 4库。

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

As always, we can get the latest version from Maven Central.

一如既往,我们可以从Maven Central获得最新版本。

5. Rules Provided in the Distribution

5.分发中提供的规则

Of course, JUnit provides a number of useful, predefined rules as part of the library. We can find all these rules in the org.junit.rules package.

当然,JUnit提供了许多有用的、预定义的规则作为库的一部分。我们可以在org.junit.rules包中找到所有这些规则。

In this section, we’ll see some examples of how to use them.

在本节中,我们将看到一些如何使用它们的例子。

5.1. The TemporaryFolder Rule

5.1.TemporaryFolder规则

When testing, we often need access to a temporary file or folder. However, managing the creation and deletion of these files can be cumbersome. Using the TemporaryFolder rule, we can manage the creation of files and folders that should be deleted when the test method terminates:

在测试时,我们经常需要访问一个临时文件或文件夹。然而,管理这些文件的创建和删除可能是很麻烦的。使用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());
}

As we can see, we first define the TemporaryFolder rule tmpFolder. Next, our test method creates a file called test-file.txt in the temporary folder. We then check that the file has been created and exists where it should. Really nice and simple!

我们可以看到,我们首先定义了TemporaryFolder规则tmpFolder。接下来,我们的测试方法在临时文件夹中创建一个名为test-file.txt的文件。然后我们检查该文件是否已经被创建并存在于它应该存在的地方。真的很好,很简单!

When the test finishes, the temporary folder and file should be deleted. However, this rule doesn’t check whether or not the deletion is successful.

当测试结束时,临时文件夹和文件应该被删除。然而,这条规则并不检查删除是否成功。

There are also a few other interesting methods worth mentioning in this class:

这一类中还有一些其他有趣的方法值得一提。

  • newFile()

    If we don’t provide any file name, then this method creates a randomly named new file.

    如果我们不提供任何文件名,那么这个方法就会创建一个随机命名的新文件。

  • newFolder(String... folderNames)

    To create recursively deep temporary folders, we can use this method.

    要创建递归的深层临时文件夹,我们可以使用这种方法。

  • newFolder()

    Likewise, the newFolder() method creates a randomly named new folder.

    同样地,newFolder()方法创建一个随机命名的新文件夹。

A nice addition worth mentioning is that starting with version 4.13, the TemporaryFolder rule allows verification of deleted resources:

值得一提的是,从4.13版本开始,TemporaryFolder规则允许验证已删除的资源。

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

If a resource cannot be deleted, the test with fail with an AssertionError.

如果一个资源不能被删除,测试将以AssertionError失败。

Finally, in JUnit 5, we can achieve the same functionality using the Temporary Directory extension.

最后,在JUnit 5中,我们可以使用Temporary Directory扩展实现同样的功能。

5.2. The ExpectedException Rule

5.2.ExpectedException规则

As the name suggests, we can use the ExpectedException rule to verify that some code throws an expected exception:

顾名思义,我们可以使用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());
}

As we can see in the example above, we’re first declaring the ExpectedException rule. Then, in our test, we’re asserting that an IllegalArgumentException is thrown.

正如我们在上面的例子中看到的,我们首先声明了ExpectedException规则。然后,在我们的测试中,我们断言一个IllegalArgumentException被抛出。

Using this rule, we can also verify some other properties of the exception, such as the message and cause.

利用这个规则,我们还可以验证异常的其他一些属性,例如消息和原因。

For an in-depth guide to testing exceptions with JUnit, check out our excellent guide on how to Assert an Exception.

有关使用 JUnit 测试异常的深入指南,请查看我们关于如何断言异常的优秀指南

5.3. The TestName Rule

5.3.测试名称规则

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

简单地说,TestName规则提供了给定测试方法中的当前测试名称:

@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

5.4.超时规则

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.

在下一个例子中,我们将看一下Timeout规则。该规则为在单个测试注解上使用超时参数提供了一个有用的选择

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.

在上面这个微不足道的例子中,我们首先为所有的测试方法定义了一个10秒的全局超时。然后,我们特意定义了一个需要超过10秒的测试。

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

5.5.错误收集器规则

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.

接下来我们要看一下ErrorCollector规则。该规则允许在发现第一个问题后继续执行测试

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

5.6.验证人规则

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.

Verifier规则是一个抽象的基类,当我们希望从测试中验证一些额外的行为时,我们可以使用它。事实上,我们在上一节看到的ErrorCollector规则扩展了这个类。

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.

在这里,我们定义了一个新的Verifier,并覆盖了verify()方法以添加一些额外的验证逻辑。在这个简单的例子中,我们只是检查一下我们的例子中的消息日志是否为空。

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

5.7.DisableOnDebug 规则

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.

有时我们可能想在调试时禁用一个规则。例如,在调试时禁用Timeout规则通常是可取的,以避免我们的测试在我们有时间正确调试之前就超时和失败。

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

DisableOnDebug规则正是这样做的,它允许我们在调试时将某些规则标记为禁用。

@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

5.8.外部资源规则

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.

通常,在编写集成测试时,我们可能希望在测试前设置一个外部资源,并在测试后将其拆除。幸好,JUnit为此提供了另一个方便的基类。

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.

我们可以扩展抽象类ExternalResource,以便在测试前设置外部资源,如文件或数据库连接。事实上,我们之前看到的TemporaryFolder规则扩展了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.

在这个例子中,当我们定义一个外部资源时,我们只需要覆盖before()方法和after()方法,以便设置和拆毁我们的外部资源。

6. Applying Class Rules

6.应用班级规则

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.

到目前为止,我们所看的所有例子都适用于单个测试用例方法。然而,有时我们可能想在测试类级别应用一个规则。我们可以通过使用@ClassRule注解来实现这一目的。

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:

这个注解的工作原理与@Rule非常相似,但它将一个规则包裹在整个测试中–主要的区别是,我们用于类规则的字段必须是静态的:

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

7. Defining a Custom JUnit Rule

7.定义一个自定义的JUnit规则

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.

正如我们所看到的,JUnit 4提供了许多开箱即用的规则。当然,我们可以定义我们自己的自定义规则。要编写一个自定义规则,我们需要实现TestRule接口。

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.

正如我们所看到的,TestRule接口包含一个名为apply(Statement, Description)的方法,我们必须覆盖该方法以返回一个Statement的实例。语句代表我们在JUnit运行时的测试。当我们调用evaluate()方法时,这将执行我们的测试。

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

在这个例子中,我们记录了一个前后的信息,并从Description对象中包含了单个测试的方法名称。

8. Using Rule Chains

8.使用规则链

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

在最后一节,我们将看看如何使用RuleChain规则对几个测试规则进行排序:

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

In the above example, we create a chain of three rules that simply print out the message passed to each MessageLogger constructor.

在上面的例子中,我们创建了一个由三个规则组成的链条,简单地打印出传递给每个MessageLogger构造器的消息。

When we run our test, we’ll see how the chain is applied in order:

当我们运行我们的测试时,我们会看到链条是如何依次应用的。

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

9. Conclusion

9.结论

To summarize, in this tutorial, we’ve explored JUnit 4 rules in detail.

总结一下,在本教程中,我们已经详细探讨了JUnit 4规则。

First, we started by explaining what rules are and how we can use them. Next, we took an in-depth look at the rules that come as part of the JUnit distribution.

首先,我们开始解释什么是规则以及我们如何使用它们。接下来,我们深入研究了作为JUnit分布的一部分的规则。

Finally, we looked at how we can define our own custom rule and how to chain rules together.

最后,我们研究了如何定义我们自己的自定义规则,以及如何将规则连锁起来。

As always, the full source code of the article is available over on GitHub.

一如既往,文章的完整源代码可在GitHub上获得