Guide to the System Rules Library – 系统规则库指南

最后修改: 2020年 9月 20日

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

1. Overview

1.概述

Sometimes when writing unit tests, we may need to test code that interacts directly with the System class. Typically in applications such as command-line tools which call System.exit directly or read arguments using System.in.

有时在编写单元测试时,我们可能需要测试直接与System类交互的代码。通常是在命令行工具等应用中,这些工具直接调用System.exit或者使用System.in读取参数。

In this tutorial, we’ll take a look at the most common features of a neat external library called System Rules which provides a set of JUnit rules for testing code that uses the System class.

在本教程中,我们将看看一个整洁的外部库的最常见功能,该库名为系统规则,它提供了一套JUnit规则,用于测试使用系统类的代码

2. Maven Dependencies

2.Maven的依赖性

First, let’s add the System Rules dependency to our pom.xml:

首先,让我们把系统规则依赖性添加到我们的pom.xml

<dependency>
    <groupId>com.github.stefanbirkner</groupId>
    <artifactId>system-rules</artifactId>
    <version>1.19.0</version>
</dependency>

We’ll also add System Lambda dependency which is available from Maven Central as well:

我们还将添加System Lambda依赖,该依赖也可以从Maven Central获得。

<dependency>
    <groupId>com.github.stefanbirkner</groupId>
    <artifactId>system-lambda</artifactId>
    <version>1.1.0</version>
</dependency>

As System Rules does not directly support JUnit5, we have added the last dependency. This provides the System Lambda wrapper methods to use in tests. There is an extensions-based alternative to this called System Stubs.

由于系统规则不直接支持JUnit5,我们添加了最后一个依赖项。这提供了系统Lambda封装方法,以便在测试中使用。有一个基于extensions的替代品,叫做System Stubs

3. Working With System Properties

3.利用系统属性工作

To quickly recap, the Java platform uses a Properties object to provide information about the local system and configuration. We can easily print out the properties:

快速回顾一下,Java平台使用Properties对象来提供有关本地系统和配置的信息。我们可以轻松地打印出这些属性。

System.getProperties()
  .forEach((key, value) -> System.out.println(key + ": " + value));

As we can see, properties include information such as the current user, the current version of the Java runtime, and file path-name separator:

正如我们所看到的,属性包括诸如当前用户、Java运行时的当前版本和文件路径名分隔符等信息。

java.version: 1.8.0_221
file.separator: /
user.home: /Users/baeldung
os.name: Mac OS X
...

We can also set our own system properties by using the System.setProperty method. Care should be taken when working with system properties from our tests, as these properties are JVM-global.

我们还可以通过使用System.setProperty方法来设置我们自己的系统属性。在使用我们的测试中的系统属性时应该小心,因为这些属性是JVM全局的。

For example, if we set a system property, we should ensure we restore the property to its original value when our test finishes or if a failure occurs. This can sometimes lead to cumbersome setup and tear down code. However, if we neglect to do this, it could lead to unexpected side-effects in our tests.

例如,如果我们设置了一个系统属性,我们应该确保在我们的测试完成后或发生故障时,将该属性恢复到其原始值。这有时会导致繁琐的设置和拆解代码。然而,如果我们忽略了这一点,可能会导致我们的测试出现意外的副作用。

In the next section, we’ll see how we can provide, clean, and make sure we restore system property values after our tests complete in a concise and simple manner.

在下一节中,我们将看到我们如何在测试完成后以简洁的方式提供、清理并确保恢复系统属性值。

4. Providing System Properties

4.提供系统属性

Let’s imagine that we have a system property log_dir which contains the location of where our logs should be written and our application sets this location when it starts up:

让我们设想一下,我们有一个系统属性log_dir,它包含了我们的日志应该被写入的位置,我们的应用程序在启动时设置了这个位置。

System.setProperty("log_dir", "/tmp/baeldung/logs");

4.1. Provide a Single Property

4.1.提供一个单一属性

Now let’s consider that from our unit test, we want to provide a different value. We can do this using the ProvideSystemProperty rule:

现在让我们考虑一下,从我们的单元测试中,我们想提供一个不同的值。我们可以使用ProvideSystemProperty规则来做到这一点。

public class ProvidesSystemPropertyWithRuleUnitTest {

    @Rule
    public final ProvideSystemProperty providesSystemPropertyRule = new ProvideSystemProperty("log_dir", "test/resources");

    @Test
    public void givenProvideSystemProperty_whenGetLogDir_thenLogDirIsProvidedSuccessfully() {
        assertEquals("log_dir should be provided", "test/resources", System.getProperty("log_dir"));
    }
    // unit test definition continues
}

Using the ProvideSystemProperty rule, we can set an arbitrary value for a given system property for use from our tests. In this example, we set the log_dir property to our test/resources directory, and from our unit test, simply assert that the test property value has been provided successfully.

使用ProvideSystemProperty规则,我们可以为给定的系统属性设置一个任意的值,以便从我们的测试中使用。在这个例子中,我们将log_dir属性设置为test/resources目录,并从我们的单元测试中简单断言测试属性值已被成功提供。

If we then print out the value of the log_dir property when our test class completes:

如果我们在测试类完成后打印出log_dir属性的值。

@AfterClass
public static void tearDownAfterClass() throws Exception {
    System.out.println(System.getProperty("log_dir"));
}

We can see the value of the property has been restored to its original value:

我们可以看到房产的价值已经恢复到了原来的价值。

/tmp/baeldung/logs

4.2. Providing Multiple Properties

4.2.提供多种属性

If we need to provide multiple properties, we can use the and method to chain as many property values together as we require for our test:

如果我们需要提供多个属性,我们可以使用and方法,根据我们的测试需要,将尽可能多的属性值连在一起。

@Rule
public final ProvideSystemProperty providesSystemPropertyRule = 
    new ProvideSystemProperty("log_dir", "test/resources").and("another_property", "another_value")

4.3. Providing Properties From a File

4.3.从文件中提供属性

Likewise, we also have the possibility of providing properties from a file or classpath resource using the ProvideSystemProperty rule:

同样,我们也可以使用ProvideSystemProperty规则从文件或classpath资源提供属性。

@Rule
public final ProvideSystemProperty providesSystemPropertyFromFileRule = 
  ProvideSystemProperty.fromResource("/test.properties");

@Test
public void givenProvideSystemPropertyFromFile_whenGetName_thenNameIsProvidedSuccessfully() {
    assertEquals("name should be provided", "baeldung", System.getProperty("name"));
    assertEquals("version should be provided", "1.0", System.getProperty("version"));
}

In the above example, we assume that we have a test.properties file on the classpath:

在上面的例子中,我们假设在classpath上有一个test.properties文件。

name=baeldung
version=1.0

4.4. Providing Properties With JUnit5 and Lambdas

4.4.用JUnit5和Lambdas提供属性

As we previously mentioned, we could also use the System Lambda version of the library to implement tests compatible with JUnit5.

正如我们之前提到的,我们也可以使用该库的System Lambda版本来实现与JUnit5兼容的测试。

Let’s see how to implement our test using this version of the library:

让我们看看如何使用这个版本的库来实现我们的测试。

@BeforeAll
static void setUpBeforeClass() throws Exception {
    System.setProperty("log_dir", "/tmp/baeldung/logs");
}

@Test
void givenSetSystemProperty_whenGetLogDir_thenLogDirIsProvidedSuccessfully() throws Exception {
    restoreSystemProperties(() -> {
        System.setProperty("log_dir", "test/resources");
        assertEquals("log_dir should be provided", "test/resources", System.getProperty("log_dir"));
    });

    assertEquals("log_dir should be provided", "/tmp/baeldung/logs", System.getProperty("log_dir"));
}

In this version, we can use the restoreSystemProperties method to execute a given statement. Inside this statement, we can set up and provide the values we require for our system properties. As we can see after this method finishes execution, the value of log_dir is the same as before /tmp/baeldung/logs.

在这个版本中,我们可以使用restoreSystemProperties方法来执行一个指定的语句。在这个语句中,我们可以为我们的系统属性设置并提供所需的值。我们可以看到在这个方法执行完毕后,log_dir的值与之前的/tmp/baeldung/logs相同。

Unfortunately, there is no built-in support for providing properties from files using the restoreSystemProperties method.

遗憾的是,对于使用restoreSystemProperties方法从文件中提供属性,没有内置支持。

5. Clearing System Properties

5.清除系统属性

Sometimes we might want to clear a set of system properties when our test starts and restore their original values when the test finishes irrespective of whether it passes or fails.

有时我们可能想在测试开始时清除一组系统属性,并在测试结束时恢复它们的原始值,而不管它是通过还是失败。

We can use the ClearSystemProperties rule for this purpose:

我们可以使用ClearSystemProperties规则来实现这一目的。

@Rule
public final ClearSystemProperties userNameIsClearedRule = new ClearSystemProperties("user.name");

@Test
public void givenClearUsernameProperty_whenGetUserName_thenNull() {
    assertNull(System.getProperty("user.name"));
}

The system property user.name is one of the predefined system properties, which contains the user account name. As expected in the above unit test, we clear this property and check it is empty from our test.

系统属性user.name是预定义的系统属性之一,它包含用户账户名。正如上述单元测试所预期的那样,我们清除了这个属性,并从我们的测试中检查它是空的。

Conveniently, we can also pass multiple property names to the ClearSystemProperties constructor.

方便的是,我们还可以向ClearSystemProperties构造函数传递多个属性名称。

6. Mocking System.in

6.嘲弄System.in

From time to time, we might create interactive command-line applications that read from System.in.

有时,我们可能会创建交互式命令行应用程序,从System.in中读取信息。

For this section, we’ll use a very simple example which reads a first name and surname from the standard input and concatenates them together:

在本节中,我们将使用一个非常简单的例子,从标准输入中读取名字和姓氏,并将它们串联起来。

private String getFullname() {
    try (Scanner scanner = new Scanner(System.in)) {
        String firstName = scanner.next();
        String surname = scanner.next();
        return String.join(" ", firstName, surname);
    }
}

System Rules contains the TextFromStandardInputStream rule which we can use to specify the lines that should be provided when calling System.in:

系统规则包含TextFromStandardInputStream规则,我们可以用它来指定调用System.in时应该提供的行。

@Rule
public final TextFromStandardInputStream systemInMock = emptyStandardInputStream();

@Test
public void givenTwoNames_whenSystemInMock_thenNamesJoinedTogether() {
    systemInMock.provideLines("Jonathan", "Cook");
    assertEquals("Names should be concatenated", "Jonathan Cook", getFullname());
}

We accomplish this by using the providesLines method, which takes a varargs parameter to enable specifying more than one value.

我们通过使用providesLines方法来实现这一目标,该方法需要一个varargs参数,以便能够指定一个以上的值。

In this example, we provide two values before calling the getFullname method, where System.in is referenced. Our two provided line values will be returned each time we call scanner.next().

在这个例子中,我们在调用getFullname方法之前提供了两个值,其中System.in被引用了。我们提供的两个行值将在每次调用scanner.next()时被返回。

Let’s take a look at how we can achieve the same in a JUnit 5 version of the test using System Lambda:

让我们来看看我们如何在JUnit 5版本的测试中使用System Lambda实现同样的效果。

@Test
void givenTwoNames_whenSystemInMock_thenNamesJoinedTogether() throws Exception {
    withTextFromSystemIn("Jonathan", "Cook").execute(() -> {
        assertEquals("Names should be concatenated", "Jonathan Cook", getFullname());
    });
}

In this variation, we use the similarly named withTextFromSystemIn method, which lets us specify the provided System.in values.

在这个变体中,我们使用类似的withTextFromSystemIn方法,它让我们指定所提供的System.in值。

It is important to mention in both cases that after the test finishes, the original value of System.in will be restored.

值得一提的是,在这两种情况下,测试结束后,System.in的原始值将被恢复。

7. Testing System.out and System.err

7.测试System.outSystem.err

In a previous tutorial, we saw how to use System Rules to unit test System.out.println().

在之前的教程中,我们看到了如何使用系统规则来进行单元测试 System.out.println().

Conveniently, we can apply an almost identical approach for testing code which interacts with the standard error stream. This time we use the SystemErrRule:

方便的是,我们可以采用几乎相同的方法来测试与标准错误流交互的代码。这一次我们使用SystemErrRule

@Rule
public final SystemErrRule systemErrRule = new SystemErrRule().enableLog();

@Test
public void givenSystemErrRule_whenInvokePrintln_thenLogSuccess() {
    printError("An Error occurred Baeldung Readers!!");

    Assert.assertEquals("An Error occurred Baeldung Readers!!", 
      systemErrRule.getLog().trim());
}

private void printError(String output) {
    System.err.println(output);
}

Nice! Using the SystemErrRule, we can intercept the writes to System.err. First, we start logging everything written to System.err by calling the enableLog method on our rule. Then we simply call getLog to get the text written to System.err since we called enableLog.

很好!使用SystemErrRule,我们可以拦截对System.err的写入。首先,我们通过调用规则的enableLog方法,开始记录所有写入System.err的内容。然后,我们简单地调用getLog来获取我们调用enableLog后写入System.err的文本。

Now, let’s implement the JUnit5 version of our test:

现在,让我们来实现JUnit5版本的测试。

@Test
void givenTapSystemErr_whenInvokePrintln_thenOutputIsReturnedSuccessfully() throws Exception {

    String text = tapSystemErr(() -> {
        printError("An error occurred Baeldung Readers!!");
    });

    Assert.assertEquals("An error occurred Baeldung Readers!!", text.trim());
}

In this version, we make use of the tapSystemErr method, which executes the statement and lets us capture the content passed to System.err.

在这个版本中,我们利用tapSystemErr方法,该方法执行语句并让我们捕捉传递给System.err的内容。

8. Handling System.exit

8.处理System.exit

Command-line applications typically terminate by calling System.exit. If we want to test such an application, it is likely that our test will terminate abnormally before it finishes when it encounters the code which calls System.exit.

命令行应用程序通常通过调用System.exit来终止。如果我们想测试这样的应用程序,当我们的测试遇到调用System.exit的代码时,很可能会在完成之前异常终止。

Thankfully, System Rules provides a neat solution to handle this using the ExpectedSystemExit rule:

值得庆幸的是,系统规则提供了一个巧妙的解决方案,使用ExpectedSystemExit规则来处理这个问题。

@Rule
public final ExpectedSystemExit exitRule = ExpectedSystemExit.none();

@Test
public void givenSystemExitRule_whenAppCallsSystemExit_thenExitRuleWorkssAsExpected() {
    exitRule.expectSystemExitWithStatus(1);
    exit();
}

private void exit() {
    System.exit(1);
}

Using the ExpectedSystemExit rule allows us to specify from our test the expected System.exit() call. In this simple example, we also check the expected status code using the expectSystemExitWithStatus method.

使用ExpectedSystemExit规则,我们可以从测试中指定预期的System.exit()调用。在这个简单的例子中,我们还使用expectSystemExitWithStatus方法检查预期状态代码。

We can achieve something similar in our JUnit 5 version using the catchSystemExit method:

我们可以在JUnit 5版本中使用catchSystemExit方法实现类似的目的

@Test
void givenCatchSystemExit_whenAppCallsSystemExit_thenStatusIsReturnedSuccessfully() throws Exception {
    int statusCode = catchSystemExit(() -> {
        exit();
    });
    assertEquals("status code should be 1:", 1, statusCode);
}

9. Conclusion

9.结语

To summarize, in this tutorial, we’ve explored the System Rules library in detail.

总而言之,在本教程中,我们已经详细地探讨了系统规则库。

First, we started by explaining how to test code that uses system properties. Then we looked at how to test the standard output and standard input. Finally, we looked at how to handle code which calls System.exit from our tests.

首先,我们首先解释了如何测试使用系统属性的代码。然后我们研究了如何测试标准输出和标准输入。最后,我们研究了如何处理测试中调用System.exit的代码。

The System Rules library also provides support for providing environment variables and special security mangers from our tests. Be sure to check out the full documentation for details.

系统规则库还提供了对提供环境变量和来自我们测试的特殊安全管理的支持。请务必查看完整的文档以了解细节。

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

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