1. 개요

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

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

2. 메이븐 의존성

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

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

Maven Central 에서도 사용할 수 있는 System Lambda 의존성을 추가 할 것입니다.

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

시스템 규칙은 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"));
}

시스템 속성 user.name 은 사용자 계정 이름을 포함하는 사전 정의된 시스템 속성 중 하나입니다. 위의 단위 테스트에서 예상한 대로 이 속성을 지우고 테스트에서 비어 있는지 확인합니다.

편리하게도 ClearSystemProperties 생성자에 여러 속성 이름을 전달할 수도 있습니다 .

6. 조롱 System.in

때때로 System.in 에서 읽는 대화형 명령줄 응용 프로그램을 만들 수 있습니다 .

이 섹션에서는 표준 입력에서 이름과 성을 읽고 함께 연결하는 매우 간단한 예를 사용할 것입니다.

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

시스템 규칙에는 System.in을 호출할 때 제공되어야 하는 행을 지정하는 데 사용할 수 있는 TextFromStandardInputStream 규칙이 포함되어 있습니다 .

@Rule
public final TextFromStandardInputStream systemInMock = emptyStandardInputStream();

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

우리는를 사용하여이를 providesLines의 A가 얻어있어서, 가변 인자 이상의 값을 지정할 수 있도록 매개 변수.

이 예 에서는 System.in 이 참조 되는 getFullname 메서드 를 호출하기 전에 두 개의 값을 제공합니다 . 우리가 Scanner.next() 를 호출할 때마다 제공된 두 줄 값이 반환됩니다 .

System Lambda를 사용하여 JUnit 5 버전의 테스트에서 동일한 결과를 얻을 수 있는 방법을 살펴보겠습니다.

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

이 변형에서는 유사한 이름의 withTextFromSystemIn  메서드를 사용하여 제공된 System.in 값을 지정할 수 있습니다 .

두 경우 모두 테스트가 완료된 후 System.in 의 원래 값 이 복원 된다는 점을 언급하는 것이 중요합니다 .

7. System.outSystem.err 테스트

이전 사용방법(예제)에서 시스템 규칙을 사용하여 System.out.println() 단위 테스트를 수행하는 방법을 보았습니다 .

편리하게도 표준 오류 스트림과 상호 작용하는 테스트 코드에 거의 동일한 접근 방식을 적용할 수 있습니다. 이번에는 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);
}

멋진! SystemErrRule을 사용하여 System.err에 대한 쓰기를 가로챌 수 있습니다 . 먼저 규칙 에서 enableLog 메서드를 호출하여 System.err기록된 모든 내용을 기록하기 시작 합니다. 그런 다음 우리는 단순히 전화 의 getLog을 우리가 호출 이후를 System.err에 기록 된 텍스트 얻을 enableLog을 .

이제 테스트의 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());
}

이 버전에서는 문을 실행하고 System.err에 전달된 내용을 캡처할 수 있는 tapSystemErr 메서드를 사용합니다 .

8. System.exit 처리

명령줄 응용 프로그램은 일반적으로 System.exit 를 호출하여 종료됩니다 . 이러한 응용 프로그램을 테스트하려는 경우 System.exit 를 호출하는 코드를 만나면 테스트가 완료되기 전에 비정상적으로 종료될 수 있습니다 .

고맙게도 시스템 규칙은 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 ExpectedSystemExit의 규칙 우리의 시험 예상에서 지정할 수있게 해준다 을 System.exit () 호출. 이 간단한 예에서 우리는 또한 expectSystemExitWithStatus 메소드를 사용하여 예상되는 상태 코드를 확인합니다 .

catchSystemExit 메소드를 사용하여 JUnit 5 버전에서 비슷한 것을 얻을 수 있습니다 .

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

9. 결론

요약하자면 이 사용방법(예제)에서는 시스템 규칙 라이브러리를 자세히 살펴보았습니다.

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

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

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

Junit footer banner