설계 과정을 지원하는 TDD
테스트를 만들려면?
1. 테스트할 기능 실행
- 클래스, 메서드, 함수이름
- 파라미터
2. 결과를 검증
- 리턴 값
이러한 과정에서 이름을 고민하고 파라미터 타입과 리턴 타입을 고민한다. 이것이 곧 설계 과정이다.
이름은 설계에서 매우 중요하며, 이름이 기대하는 것과 다르게 동작할 경우 코드 분석 시간을 중가시키므로 잘 정하는 것이 중요하다
테스트 코드 작성 시,
파라미터와 결과 값을 정해야함 → 요구사항 문서에서 기능의 입력과 결과 도출 필요
구현 시 애매한 점의 경우 구체적으로 정리 필요
→ 구체적인 예를 통해서 모호함이 줄어들고, 구체적인 명세가 된다
주의 사항 - 필요할 것으로 예측해서 미리 코드를 만들면 안됨 (설계도 마찬가지)
요구사항 분석 과정에서 설계를 진행
JUnit 5
JUnit5의 요소
● JUnit 플랫폼
: 테스팅 프레임워크를 구동하기 위한 런처와 테스트 엔진을 위한 API 제공
○ junit-platform-launcher ▷ junit-platform-engine
● JUnit 주피터(Jupiter)
: JUnit5를 위한 테스트 API와 실행엔진 제공
○ junit-jupiter-engine ▷ junit-jupiter-api
● JUnit 빈티지(Vintage)
: JUnit 3과 4로 작성된 테스트를 JUnit5 플랫폼에서 실행하기 위한 모듈을 제공
○ junit-vintage-engine ▷ junit:junit
JUnit5 는 테스트를 위한 API로 주피터 API 제공
사용하기 위해서 주피터 관련 모듈 의존에 추가
1. junit-jupiter-api
2. junit-jupiter-params
3. junit-jupiter-engine
JUnit5를 이용해서 테스트 실행할 경우 JUnit5가 제공하는 플랫폼 런처 사용 필요
maven의 경우 → maven-surefire-plugin 2.22.0 버전부터 JUnit5 플랫폼 지원
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
...
</plugins>
</build>
JUnit의 Assertions 클래스
값을 검증하기 위한 목적의 다양한 정적 메서드 제공
주요 단언 메서드 정리
| 메서드 | 설명 |
| assertEquals(expected, actual) | 실제 값(actual)이 기대하는 값(expected)와 같은 지 검사 |
| assertNotEquals(unexpected, actual) | 실제 값(actual)이 특정 값(unexpected)과 같지 않은 지 검사 |
| assertSame(Object expected, Object actual) | 두 객체가 동일한 객체인지 검사 |
| assertNotSame(Object unexpected, Object actual) | 두 객체가 동일하지 않은 객체인지 검사 |
| assertTrue(boolean condition) | 값이 true 인지 검사 |
| assertFalse(boolean condition) | 값이 false인지 검사 |
| assertNull(Object actual) | 값이 null인지 검사 |
| assertNotNull(Object actual) | 값이 null이 아닌지 검사 |
| fail() | 테스트를 실패 처리 |
fail() 메서드의 경우, 테스트에 실패했음을 알리고 싶을 때 사용 - 특히 익셉션 발생 유무를 검증할 때 많이 사용
익셉션 발생 유무 검사 메서드
| 메서드 | 설명 |
| assertThrows(Class<T> expectedType, Executable executable) | executable을 실행한 결과로 지정한 타입의 익셉션이 발생하는지 검사 |
| assertDoesNotThrow(Executable executable) | executable을 실행한 결과로 익셉션이 발생하지 않는지 검사 |
예시 코드
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> {
AuthService authService = new AuthService();
authService.authenticate(null, null);
});
// 추가 검증
assertTrue(thrown.getMessage().contains("id"));
※ 참고 - Executable 인터페이스
: execute 함수를 가진 함수형 인터페이스
package org.junit.jupiter.api.function;
public interface Executable{
void execute() throws Throwable;
}
assertAll()
일단 모든 검증을 실행하고 그중에 실패한 것이 있는지 확인
Executable 목록을 가변인자로 받아서 각 Executable을 수행하는 것
assertAll(
() -> assertEquals(3, 5/2),
() -> assertEquals(4, 2*2),
() -> assertEquals(6, 11/2)
);
테스트 라이프사이클
@BeforeEach, @AfterEach
1. 테스트 메서드를 포함한 객체 생성
2. @BeforeEach 존재 시, 해당 어노테이션이 붙은 메서드 실행
3. @Test 어노테이션이 붙은 메서드 실행
4. @AfterEach 존재 시, 해당 어노테이션이 붙은 메서드 실행
※ @Test, @BeforeEach, @AfterEach 어노테이션을 붙인 메서드는 private이면 안됨
@BeforeAll, @AfterAll
한 클래스의 모든 테스트 메서드가 실행되기 전/후에 특정 작업을 수행해야 하는 경우
JUnit은 테스트 메서드의 실행 순서를 지정할 순 있지만, 각 테스트 메서드는 서로 독립적으로 동작해야하는 것임. 의존성이 생길경우 유지보수에 어려움이 생김
++)
@DisplayName : 메서드 이름 붙이기
@Disabled : 특정 테스트를 실행하지 않고 싶을 때 -- 테스트 코드가 완성되지 않았거나 잠시동안 테스트를 실행하지 말아야 할 때
import org.junit.jupiter.api.Disabled;
public class AssertionsTest {
@Disabled
@Test
void failMethod() {
try{
AuthService authService = new AuthService();
authService.authenticate(null, null);
fail();
} catch (IllegalArgumentException e) {
}
}
}
모든 테스트를 실행하는 방법
● maven : mvn test
● gradle : gradle test