Contents
1. 조건에 따른 테스트 실행
1) 시스템 환경변수에 따른 실행

시스템 환경변수에 따라서 테스트 실행 여부를 결정할 수 있다.
class RobotConfigTest {
@Test
@DisplayName("시스템 환경변수 값에 따른 실행 테스트")
void create_new_robot(){
String test_env = System.getenv("ENV");
System.out.println(test_env);
assertEquals("LOCAL", test_env);
}
}
실행해보면 값이 다르다.

이때 시스템 환경변수를 바꾸면 인텔리제이도 재시작해야한다.
환경변수 값을 Local로 바꾸고, 조건에 따라 실행을 다르게 했다.
(1) assumingThat으로 코드로 조건 분기
@Test
@DisplayName("시스템 환경변수 값에 따른 실행 테스트")
void create_new_robot(){
String test_env = System.getenv("ENV");
assumingThat(test_env.equalsIgnoreCase("LOCAL"), () -> {
System.out.println("local");
});
assumingThat(test_env.equalsIgnoreCase("DEV"), () -> {
System.out.println("dev");
});
}
이 값을 가지고 다시 테스트해보았다.

(2) 어노테이션으로 조건 주기
@EnabledIfEnvironmentVariable(named = "환경변수명", matches = "값")을 사용한다.
@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "LOCAL")
@DisplayName("로컬 환경변수일 때만 실행")
void excute_local_env(){
System.out.println("local");
}
@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "DEV")
@DisplayName("DEV 환경변수일 때만 실행")
void excute_dev_env(){
System.out.println("dev");
}
기존의 @EnabledIf는 deprecated 됨.
2) Os 따라 실행
어노테이션 EnabledOnOs로 Os 조건에 따라 실행할 수도 있다.
Os는 배열로도 적을 수 있다.
@Test
@EnabledOnOs(OS.WINDOWS)
@DisplayName("윈도우일 때만 실행")
void execute_if_win(){
System.out.println("win");
}
@Test
@EnabledOnOs(OS.MAC)
@DisplayName("맥일 때만 실행")
void execute_if_mac(){
System.out.println("mac");
}
@Test
@EnabledOnOs({OS.MAC, OS.LINUX, OS.SOLARIS})
@DisplayName("윈도우가 아닌 몇몇 경우만 실행")
void execute_if_not_win(){
System.out.println("not win");
}

조건에 따라 실행되지 않은 테스트가 보인다.
3) 자바 버젼에 따른 실행
@Test
@EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10, JRE.JAVA_11, JRE.JAVA_17})
@DisplayName("java 8 이상")
void execute_jre_above_8(){
System.out.println("JRE above 8");
}
@Test
@EnabledOnJre({JRE.OTHER})
@DisplayName("java 8 이상이 아닌 경우")
void execute_jre_under_8(){
System.out.println("JRE under 8");
}
4) config 파일에 따른 실행
test 파일에서는 Java 파일로 binding하여 Autowired 하는 방식은 null 값이 떠서 아래 방법을 사용했는데
좀 더 나은 방법이 있을 지 확인 중이다.
server.address.ip: "192.168.0.1"
server.resources_path.imgs: "/imgs"
app:
type: "EU"
application.yml 파일을 작성하고, test 파일에는
@EnableConfigurationProperties
@SpringBootTest(classes=테스트용 클래스)를 넣어서 실행했다.
@EnableConfigurationProperties
@SpringBootTest(classes=Robot.class)
class ExecuteByConfigTest {
@Value("${app.type}")
private String type;
@Test
void execute_by_config(){
assertEquals(Region.EU.name(), type);
if(type.equals(Region.EU.name())){
System.out.println(Region.EU.name());
}else{
System.out.println(Region.N_EU.name());
}
}
}
2. 필터링 방법
원하는 테스트 그룹만 필터링 해서 테스트 하는 기능이다.
- 개발툴 (인텔리제이 등) 설정
- 메이븐에서 플러그인을 설정
- 그래들 타입 설정
으로 실행해볼 수 있다.
예를 들어 실행이 빠른 것과 느린 것을 구분하여, 빠른 것은 로컬, 느린 것은 개발 서버에서 테스트 할 수 있다.
- 메소드에 @Tag를 추가하여 필터링 하고, 여러 태그를 쓸 수 있다.
class TagingTest {
@Test
@Tag("fast")
@Tag("multi")
@DisplayName("속도가 빠른 테스트")
void create_new_robot_fast(){
assertTimeoutPreemptively(Duration.ofMillis(100), () -> {
new Robot(10);
Thread.sleep(10);
});
}
@Test
@Tag("slow")
@Tag("single")
@DisplayName("속도 느린 테스트")
void create_new_robot_slow(){
assertTimeoutPreemptively(Duration.ofMillis(100), () -> {
new Robot(10);
Thread.sleep(1000);
});
}
1) 인텔리제이 태그 필터링 설정
소스를 작성하고, 인텔리제이에서 설정해준다.


속도가 빠른 테스트 (fast)로 태깅한 것만 실행이 된다.
2) 메이븐에서 테스트 필터링 하는 방법
프로파일에서 디폴트 프로파일을 만들어서 플로그인 설정을 한다.
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDfault>true</activeByDefault>
</activation>
<build>
<plugin>
<artifactId>원하는 이름</artifactId>
<configuration>
<groups>fast | slow</groups>
</configuration>
</plugin>
</build>
</profile>
<profile>
<id>ci</id>
<build>
<plugin>
<artifactId>원하는 이름</artifactId>
</plugin>
</build>
</profile>
</profiles>
플러그인을 추가하면 된다. (ci는 운용용)
!는 not, &는 and, |는 or이다.
<plugin>
<artifactId>원하는 이름</artifactId>
<configuration>
<groups>fast</groups>
</configuration>
</plugin>
콘솔에서 ./mvnw test -P ci 형태로 실행한다.
3) 그래들에서 테스트 필터링 하는 방법
build.gradle 파일에 들어가면 기본적으로 아래 설정이 되어있다.
tasks.named('test') {
useJUnitPlatform()
}
includeTags, excludeTags를 이용해서 포함하고 제외할 태그를 설정할 수 있다.
tasks.named('test') {
useJUnitPlatform {
includeTags 'slow'
excludeTags 'multi'
}
}
그런데 이렇게 되면 test라는 이름의 하나의 task만 사용하게 되기 떄문에, 다른 task를 구성해서 구분해주는 것이 좋다.
// task 분리
// `gradle test`: integration tag가 붙은 테스트는 제외
tasks.named('test') {
useJUnitPlatform {
excludeTags 'dev', 'prod'
}
}
task devTest(type: Test) {
useJUnitPlatform {
includeTags 'dev'
}
}
task prodTest(type: Test) {
useJUnitPlatform {
includeTags 'prod'
}
}
- gradle test : dev, prod 제외하고 실행
- gradle dev : devTest 실행
class TagingTest {
@Test
@Tag("local")
@Tag("fast")
@Tag("multi")
@DisplayName("속도가 빠른 테스트")
void create_new_robot_all(){
System.out.println("tag - local 테스트");
assertTrue(1==1, "테스트 용도");
}
@Test
@Tag("dev")
@Tag("fast")
@Tag("multi")
@DisplayName("속도가 빠른 테스트")
void create_new_robot_fast(){
System.out.println("tag - dev 테스트");
assertTimeoutPreemptively(Duration.ofMillis(100), () -> {
new Robot(10);
Thread.sleep(10);
});
}
@Test
@Tag("prod")
@Tag("slow")
@Tag("single")
@DisplayName("속도 느린 테스트")
void create_new_robot_slow(){
System.out.println("tag - prod 테스트");
assertTimeoutPreemptively(Duration.ofMillis(100), () -> {
new Robot(10);
Thread.sleep(1000);
});
}
}
새 프로젝트를 만들면서 java17, spring boot 3.0을 썼기 때문에 기존 설치한 그래들과 자바버젼 변경은 번거로워서 결과 확인은 콘솔이 아닌 인텔리제이에서 했다.
gradle test로 실행하면 dev, prod를 제외한 모든 테스트가 실행되고

gradle dev로 실행하면, @Tag("dev")로 설정된 것만 테스트 된다.

** 참고
- https://maven.apache.org/guides/introduction/introduction-to-profiles.html
- https://junit.org/junit5/docs/current/user-guide/#running-tests-tag-expressions
3) 커스텀 태그 만들기
- 어노테이션을 추가하여 커스텀 태그를 만들 수 있다. 어노테이션 만드는 법은 스프링에서 전부 동일하다.
- 커스텀 태그도 여러 태그를 동시에 쓸 수 있다.
- @Tag("문자열")은 오타가 날 수도 있고 타입이 안전하지 않기 때문에, 커스텀 태그를 만드는 쪽이 안전하다.
@Test
@Target(ElementType.METHOD) // 메소드에 사용
@Retention(RetentionPolicy.RUNTIME) // 어노테이션 정보가 런타임까지 유지
@Tag("dev") // "dev"는 문자열이므로 타입이 안전하지 않음. (오타 발생 많음)
public @interface DevTest {
}
class CustomTagTest {
@Test
@LocalTest
void create_new_robot_all(){
System.out.println("tag - local 테스트");
assertTrue(1==1, "테스트 용도");
}
@Test
@DevTest
@DisplayName("속도가 빠른 테스트")
void create_new_robot_fast(){
System.out.println("tag - dev 테스트");
}
@Test
@ProdTest
@DisplayName("속도 느린 테스트")
void create_new_robot_slow(){
System.out.println("tag - prod 테스트");
}
}
3. 반복 테스트
여러번 검증을 위해서 테스트를 반복할 수 있다.
- @RepeatedTest : 반복 횟수와 반복 테스트 이름을 설정할 수 있다.
- @ParameterizedTest : 테스트에 여러 다른 매개변수를 대입해가며 반복 실행한다.
@RepeatedTest
- 반복 횟수(value)와 테스트명(name)을 설정할 수 있고, name에 {displayName}, {currentRepetition}, {totalRepetitions}을 넣을 수 있다.
// @RepeatedTest(10) // (횟수)
@RepeatedTest(value=10, name = "{displayName}, {currentRepetition} / {totalRepetitions}")
@DisplayName("반복 테스트")
void repeatAfterMe(RepetitionInfo info){
System.out.println("test : " + info.getCurrentRepetition() + "/" + info.getTotalRepetitions());
}

@ParameterizedTest
- 여러 다른 매개변수를 대입해서 반복 실행 시에 사용한다.
- 역시 테스트명(name)을 설정할 수 있고, name에 {displayName}, {index}, {arguments}, 파라미터 {0}, {1},을 넣을 수 있다.
- 횟수는 매개변수에 따라 다르므로 설정x
- 매개변수는 아래로 셋팅할 수 있다.
- @ValueSource
- @NullSource, @EmptySource, @NullAndEmptySource
- @EnumSource
- @MethodSource
- @CsvSource
- @CvsFileSource
- @ArgumentSource
- 위의 태그로 준 매개변수들은 태그 순서에 따라 위에서부터 순차적으로 실행된다
(1) @ValueSource
- strings, boolean, classes 등 자료형 배열을 넣을 수 있다.
@ParameterizedTest(name = "{index} : {displayName} message= {0}")
@ValueSource(strings = {"우리", "회사", "화이팅"} )
@DisplayName("반복 테스트 - Parameterized")
void repeatByParameters(String message){
System.out.println(message);
assertTrue(message.length()>2, () -> "길이가 두 글자를 초과해야합니다.");
}

(2) @NullSource, @EmptySource, @NullAndEmptySource
- @NullSource : null 소스를 하나 더 넣어줌
@ParameterizedTest(name = "{index} : {displayName} message= {0}")
@ValueSource(strings = {"우리", "회사", "화이팅"} )
@NullSource
@DisplayName("반복 테스트 - Parameterized")
void repeatByParameters(String message){
System.out.println(message);
assertTrue(message.length()>1, () -> "길이가 두 글자를 초과해야합니다.");
}

length체크를 하기 때문에, null을 마지막 인자로 넣어주면 테스트 실패
- @EmptySource : 빈 소스를 하나 더 넣어줌.
@ParameterizedTest(name = "{index} : {displayName} message= {0}")
@ValueSource(strings = {"우리", "회사", "화이팅"} )
@EmptySource
@NullSource
@DisplayName("반복 테스트 - Parameterized")
void repeatByParameters(String message){
System.out.println(message);
assertTrue(message.length()>1, () -> "길이가 두 글자를 초과해야합니다.");
}

- @NullAndEmptySource : Null과 Empty를 순차적으로 넣어준다.
Null과 빈 값을 테스트할 때 유용할 것 같다.
@ParameterizedTest(name = "{index} : {displayName} message= {0}")
@NullAndEmptySource
@ValueSource(strings = {"우리", "회사", "화이팅"} )
// @EmptySource
// @NullSource
@DisplayName("반복 테스트 - Parameterized")
void repeatByParameters(String message){
System.out.println(message);
assertTrue(message.length()>1, () -> "길이가 두 글자를 초과해야합니다.");
}

다만 @NullAndEmptySource와 @NullSource, @EmptySource들이 동시에 먹지는 않는다.
아래처럼 다 넣어서 테스트해도 위에서 한번만 실행된다.
@ParameterizedTest(name = "{index} : {displayName} message= {0}")
@NullAndEmptySource
@ValueSource(strings = {"우리", "회사", "화이팅"} )
@EmptySource
@NullSource
@DisplayName("반복 테스트 - Parameterized")
void repeatByParameters(String message){
System.out.println(message);
assertTrue(message.length()>1, () -> "길이가 두 글자를 초과해야합니다.");
}

(3) @EnumSource
(4) @MethodSource
(5) @CsvSource
- 여러 인자를 ,(comma)를 이용해서 넣어줄 수 있다.
(6) @CvsFileSource
(7) @ArgumentSource
인자 값 타입 변환
- @ParameterizedTest를 사용하면 파라미터를 원하는 대로 받을 수 있는데 이때 유용하다.
(1) 암묵적 타입 변환
: 암묵적으로 타입을 내가 받고자 하는 타입으로 변환을 해준다.
https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests-argument-conversion-implicit
JUnit 5 User Guide
Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo
junit.org
"true"로 값이 들어오면 파라미터 타입에 따라 boolean인 true로 암묵적으로 변환을 해준다.
(2) 명시적 타입 변환 - 단일 인자
기본 데이터형의 경우 그 값이 어떤 값인지 특정하기 어렵기 때문에, 특정 객체로 자료형을 나타내는 것이 좋다.
이때 SimpleArgumentConverter 상속 받아서 컨버터를 구현해서 사용하면 좋다.
- 변환하려는 파라미터 앞에 @ConvertWith를 붙여준다.
// SimpleArgumentConvert는 하나의 argument만 사용할 수 있음.
public class RobotConverter extends SimpleArgumentConverter {
@Override
protected Object convert(Object source, Class<?> targetType) throws ArgumentConversionException {
assertEquals(Robot.class, targetType, "Can only convert to Robot");
return new Robot(Integer.parseInt(source.toString()));
}
}
// SimpleArgumentConvert로 단일 인자값을 받아서 convert (명시적 타입 지정)
@ParameterizedTest(name = "{index} : {displayName} message= {0}")
@ValueSource(ints = {11, 12, 13})
@DisplayName("반복 Parameterized - SimpleArgumentConvert")
void repeatByMultiValues(@ConvertWith(RobotConverter.class) Robot robot){
assertTrue(robot.getTimeout()>11, () -> "Timeout은 10보다 커야합니다.");
}

- 다만 SimpleArgumentConverter는 하나의 argument에 대해서만 사용 가능.
인자값을 여러개 쓰려면 ArgumentsAccessor를 이용한다.
(3) 명시적 타입 변환 - 인자 여러개 조합
- ArgumentsAccessor를 이용해서 구현한다.
우선 로봇 객체에 name 변수를 하나 더 추가해준다.
@NoArgsConstructor
@Getter
@ToString
public class Robot {
private String name = "unused";
private RobotStatus status = RobotStatus.AWAITING;
private int timeout;
public Robot(int timeout){
if(timeout < 0) throw new IllegalArgumentException("timeout 설정값은 0보다 커야합니다.");
this.timeout = timeout;
}
public Robot(int timeout, String name){
this.name = name;
this.timeout = timeout;
}
파라미터로 ArgumentsAggregator를 받는다.
@ParameterizedTest(name = "{index} : {displayName} message= {0}")
@CsvSource({"11, '우리'", "12, '모두'","13, '직원'"} )
@DisplayName("반복 Parameterized - ArgumentsAggregator 사용")
void repeatUsingArgumentsAccessor(ArgumentsAccessor accessor){
Robot robot = new Robot(accessor.getInteger(0), accessor.getString(1));
System.out.println(robot.toString());
}

- ArgumentsAggregator 인터페이스를 class로 분리해서 구현할 수도 있다.
다만 public클래스나 static inner 클래스로 작성해야 한다.
// 파라미터 인자를 여러개 사용할 경우
// 제약 조건 : public 클래스 이거나 static inner 클래스로 만들어야 함
public class RobotAggregator implements ArgumentsAggregator {
@Override
public Robot aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) throws ArgumentsAggregationException {
return new Robot(accessor.getInteger(0), accessor.getString(1));
}
}
ArgumentAggregator를 구현한 class를 작성한 후,
@ParameterizedTest(name = "{index} : {displayName} message= {0}")
@CsvSource({"11, '우리'", "12, '모두'","13, '직원'"} )
@DisplayName("반복 Parameterized - custom ArgumentsAggregator 사용")
void repeatUsingConverter(@AggregateWith(RobotAggregator.class) Robot robot){
System.out.println(robot.toString());
}
테스트 소스에서 @AggregateWith(클래스 명)을 적으면 된다.

출처 : '더 자바, 애플리케이션을 테스트하는 다양한 방법'(백기선), 자료&6~10강
'Java & Spring' 카테고리의 다른 글
| [Java Test] 4. Jmeter 성능테스트 (0) | 2023.05.25 |
|---|---|
| [Java Test] 2. Mockito (0) | 2023.05.22 |
| [Java Test] 1. JUnit5 (4) properties, 확장, 마이그레이션 (0) | 2023.05.22 |
| [Java Test] 1. JUnit5 (3) 테스트 인스턴스 & 순서 지정 (0) | 2023.05.19 |
| [Java Test] 1. JUnit5 (1) (0) | 2023.05.18 |
| [DDD] 도메인 주도 개발 - (3) 리포지토리 & 모델 구현 (0) | 2023.02.21 |
| [DDD] 도메인 주도 개발 - (2) 애그리거트 (0) | 2023.02.21 |
| [DDD] 도메인 주도 개발 - (1) 좋은 아키텍처와 도메인 주도 설계 (0) | 2023.01.11 |
