JUnit-Rule

Rule은 하나의 테스트 클래스 내에서 동작 방식을 재정의 하거나 추가하기 위해 사용됩니다. 특히 다른 테스트클래스에서도 재사용성할 때 유용합니다.

기본 Rule 클래스

규칙이름 설명
TemporaryFolder 임시폴더 관리. 테스트 후 삭제
ExternalResources 자원(DB, 파일, 소켓) 관리
ErrorCollector 지속적 테스트 실패 수집
Verifier 별개 조건 확인 (vs assert*)
TestWatcher 테스트 인터셉터 (starting, succeeded, failed, finished…)
TestName 테스트 메소드명을 알려줌
Timeout 테스트 클래스 전역 timeout 설정 (vs @Timeout)
ExpectedException 예외 직접 확인 (vs @Expected)
DisableOnDebug Rule 디버그 비활성화 데코레이터
RuleChain 복수 Rule chaining 복합체
ClassRule 테스트슈트 전체에 Rule 적용

상속구조

TemporaryFolder

public static class HasTempFolder {
  @Rule
  public TemporaryFolder folder = new TemporaryFolder();

  @Test
  public void testUsingTempFolder() throws IOException {
    File createdFile = folder.newFile("myfile.txt");
    File createdFolder = folder.newFolder("subfolder");
    // ...
  }
}

ExternalResources

외부 자원(파일, 소켓, DB커넥션 등) 관리. @Before, @After과 별 차이가 없으나, 특정 자원이 다른 테스트케이스에서 재사용성이 요구될 경우 유용함

public static class UsesExternalResource {
  Server myServer = new Server();

  @Rule
  public ExternalResource resource = new ExternalResource() {
    @Override
    protected void before() throws Throwable {
      myServer.connect();
    };

    @Override
    protected void after() {
      myServer.disconnect();
    };
  };

  @Test
  public void testFoo() {
    new Client().run(myServer);
  }
}

ErrorCollector

오류를 계속 쌓아둘 수 있으며, 오류가 발생해도 테스트를 계속 진행시킬 수 있을 때 유용하다.

  • checkThat
  • checkSucceeds
  • addError
public static class UsesErrorCollectorTwice {
  @Rule
  public ErrorCollector collector = new ErrorCollector();

  @Test
  public void example() {
    collector.addError(new Throwable("first thing went wrong"));
    collector.addError(new Throwable("second thing went wrong"));
  }

  @Test
  public void testGetAmount() throws Exception {
    Order order1 = ...
    collector.checkThat(10000, order1.getAmount());  // 오류가 발생해도 아래 줄 실행됨
    Order order2 = ...
    collector.checkThat(20000, order2.getAmount());
  }
}

Verifier

테스트 자체를 검증하는 assert* 메소드와는 달리, 일반적으로 테스트케이스 실행 후 만족해야하는 환경조건이나 Global조건(객체들의 종합 상태)을 검사하는데 쓰인다.

private static String sequence;

public static class UsesVerifier {
  @Rule
  public Verifier collector = new Verifier() {
    @Override
    protected void verify() {
      sequence += "verify ";
    }
  };

  @Test
  public void example() {
    sequence += "test ";
  }

  @Test
  public void verifierRunsAfterTest() {
    sequence = "";
    assertThat(testResult(UsesVerifier.class), isSuccessful());
    assertEquals("test verify ", sequence);
  }
}

TestWatcher

  • apply
  • succeeded
  • failed
  • skipped
  • starting
  • finished

TestName

그냥 테스트 이름 호출할 뿐

public class NameRuleTest {
  @Rule
  public TestName name = new TestName();

  @Test
  public void testA() {
    assertEquals("testA", name.getMethodName());
  }
}

Timeout

테스트 클래스 전역 timeout 설정

public static class HasGlobalTimeout {
  public static String log;

  @Rule
  public TestRule globalTimeout = new Timeout(20);

  @Test
  public void testInfiniteLoop1() {
    log += "ran1";
    for(;;) {}
  }

  @Test
  public void testInfiniteLoop2() {
    log += "ran2";
    for(;;) {}
  }
}

ExpectedException

@Expected는 단순하게 클래스 타입만 검증하면 메세지도 검증가능하며, 확장하면 코드성 Exception 검증도 가능

public static class HasExpectedException {
  @Rule
  public ExpectedException thrown = ExpectedException.none();

  @Test
  public void throwsNothing() {
  }

  @Test
  public void throwsNullPointerException() {
    thrown.expect(NullPointerException.class);
    throw new NullPointerException();
  }

  @Test
  public void throwsNullPointerExceptionWithMessage() {
    thrown.expect(NullPointerException.class);
    thrown.expectMessage("happened?");
    thrown.expectMessage(startsWith("What"));
    throw new NullPointerException("What happened?");
  }
}

DisableOnDebug

-Xdebug 또는 -agentlib:jdwp 옵션 시 비활성화

@Rule
public TestRule timeout = new DisableOnDebug(new Timeout(20));

RuleChain

말 그대로 Rule chaining

public static class UseRuleChain {
    @Rule
    public TestRule chain = RuleChain
                           .outerRule(new LoggingRule("outer rule"))
                           .around(new LoggingRule("middle rule"))
                           .around(new LoggingRule("inner rule"));

    @Test
    public void example() {
        assertTrue(true);
    }
}

ClassRule

@RunWith(Suite.class)
@SuiteClasses({A.class, B.class, C.class})
public class UsesExternalResource {
  public static Server myServer = new Server();

  @ClassRule
  public static ExternalResource resource = new ExternalResource() {
    @Override
    protected void before() throws Throwable {
      myServer.connect();
    };

    @Override
    protected void after() {
      myServer.disconnect();
    };
  };
}

참고로 커스텀 룰 구현도 가능합니다.

https://github.com/junit-team/junit4/wiki/Rules#custom-rules

참조

MJ

MJ
Backend 개발자 사람입니다. 어플리케이션의 복잡성을 다루는 DDD에 관심이 많습니다. 어제보다 더 나은 개발자가 되려고 항상 노력합니다.

IDDD 14장. 애플리케이션

* 사용자 인퍼페이스와 도메인 모델 사이에 위치 * 유스케이스 태스크를 조정* 트랜잭션, 보안 권한 부여 담당> 애플리케이션을 핵심 도메인 모델과 상호 교류하며 이를 지원하기 위해 잘 조합된 컴포넌트의 집합을 의미하기 위해 사용하고 있다.>...… Continue reading

IDDD 12장. 리파지토리

Published on June 10, 2018