시스템 규칙 라이브러리 안내

1. 개요

때때로 단위 테스트를 작성할 때 System 클래스 와 직접 상호 작용하는 코드를 테스트해야 할 수도 있습니다 . 일반적으로 System.exit를 직접 호출 하거나 System.in을 사용하여 인수를 읽는 명령 줄 도구와 같은 응용 프로그램에서 사용 됩니다.

이 튜토리얼 에서는 System 클래스 를 사용하는 코드를 테스트하기위한 JUnit 규칙 세트를 제공하는 System Rules라는 깔끔한 외부 라이브러리의 가장 일반적인 기능을 살펴볼 것 입니다.

2. Maven 종속성

먼저 pom.xml에 시스템 규칙 종속성을 추가해 보겠습니다 .

 com.github.stefanbirkner system-rules 1.19.0 

또한 Maven Central에서 사용할 수있는 System Lambda 종속성도 추가합니다.

 com.github.stefanbirkner system-lambda 1.1.0 

시스템 규칙은 JUnit5를 직접 지원하지 않으므로 마지막 종속성을 추가했습니다. 이는 테스트에 사용할 시스템 Lambda 래퍼 메서드를 제공합니다. 시스템 스텁이라고하는 확장 기반 대안이 있습니다.

3. 시스템 속성 작업

빠르게 요약하기 위해 Java 플랫폼은 Properties 객체를 사용 하여 로컬 시스템 및 구성에 대한 정보를 제공합니다. 속성을 쉽게 인쇄 할 수 있습니다.

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

보시다시피 속성에는 현재 사용자, 현재 버전의 Java 런타임 및 파일 경로 이름 구분 기호와 같은 정보가 포함됩니다.

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

System.setProperty 메서드 를 사용하여 자체 시스템 속성을 설정할 수도 있습니다 . 이러한 속성은 JVM 전역이므로 테스트에서 시스템 속성으로 작업 할 때는주의해야합니다.

예를 들어 시스템 속성을 설정 한 경우 테스트가 완료되거나 실패가 발생하면 속성을 원래 값으로 복원해야합니다. 이로 인해 때때로 번거로운 설정 및 코드 해체가 발생할 수 있습니다. 그러나이를 무시하면 테스트에서 예기치 않은 부작용이 발생할 수 있습니다.

다음 섹션에서는 테스트가 간결하고 간단한 방식으로 완료된 후 시스템 속성 값을 제공하고 정리하고 복원하는 방법을 살펴 보겠습니다.

4. 시스템 속성 제공

로그를 작성해야하는 위치를 포함 하는 시스템 속성 log_dir 이 있고 애플리케이션이 시작될 때이 위치를 설정 한다고 가정 해 보겠습니다 .

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

4.1. 단일 속성 제공

이제 단위 테스트에서 다른 값을 제공하고자한다고 생각해 보겠습니다. 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 ProvideSystemProperty의 규칙을, 우리는 우리의 테스트에서 사용하기 위해 주어진 시스템 속성에 대한 임의의 값을 설정할 수 있습니다. 이 예제에서는 log_dir 속성을 test / resources 디렉터리로 설정하고 단위 테스트에서 테스트 속성 값이 성공적으로 제공되었다고 단언합니다.

그런 다음 테스트 클래스가 완료 될 때 log_dir 속성 의 값을 출력하면 :

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

속성 값이 원래 값으로 복원 된 것을 볼 수 있습니다.

/tmp/baeldung/logs

4.2. 여러 속성 제공

여러 속성을 제공해야하는 경우 메서드를 사용하여 테스트에 필요한만큼의 속성 값을 연결할 수 있습니다 .

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

4.3. 파일에서 속성 제공

마찬가지로 ProvideSystemProperty 규칙을 사용하여 파일 또는 클래스 경로 리소스에서 속성을 제공 할 수도 있습니다 .

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

위의 예에서는 클래스 경로에 test.properties 파일 이 있다고 가정합니다 .

name=baeldung version=1.0

4.4. JUnit5 및 Lambda로 속성 제공

앞서 언급했듯이 System Lambda 버전의 라이브러리를 사용하여 JUnit5와 호환되는 테스트를 구현할 수도 있습니다.

이 버전의 라이브러리를 사용하여 테스트를 구현하는 방법을 살펴 보겠습니다.

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

이 버전에서는 주어진 문을 실행하기 위해 restoreSystemProperties 메소드를 사용할 수 있습니다 . 이 문 내에서 시스템 속성에 필요한 값을 설정하고 제공 할 수 있습니다 . 이 메서드가 실행을 마친 후 알 수 있듯이 log_dir 의 값은 / tmp / baeldung / logs 이전과 동일 합니다.

불행히도 restoreSystemProperties 메소드를 사용하여 파일에서 특성을 제공하기위한 기본 제공 지원이 없습니다 .

5. 시스템 속성 지우기

때로는 테스트가 시작될 때 시스템 속성 집합을 지우고 통과 또는 실패 여부에 관계없이 테스트가 완료되면 원래 값을 복원하려고 할 수 있습니다.

이를 위해 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.

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

6. Mocking System.in

From time to time, we might create interactive command-line applications that read from 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:

@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.

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().

Let's take a look at how we can achieve the same in a JUnit 5 version of the test using 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.

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

7. Testing System.out and System.err

In a previous tutorial, we saw how to use System Rules to unit test 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:

@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.

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

@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.

8. Handling 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.

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

@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.

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

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

9. 결론

요약하면이 튜토리얼에서는 시스템 규칙 라이브러리를 자세히 살펴 보았습니다.

먼저 시스템 속성을 사용하는 코드를 테스트하는 방법부터 설명했습니다. 그런 다음 표준 출력과 표준 입력을 테스트하는 방법을 살펴 보았습니다. 마지막으로 테스트에서 System.exit 를 호출하는 코드를 처리하는 방법을 살펴 보았습니다 .

시스템 규칙 라이브러리는 또한 테스트에서 환경 변수 및 특수 보안 관리자를 제공하기위한 지원을 제공합니다 . 자세한 내용은 전체 문서를 확인하십시오.

항상 그렇듯이 기사의 전체 소스 코드는 GitHub에서 사용할 수 있습니다.