- 스프링 & JUnit5, 테스트 주도 개발
작년 가을 JUnit5 정식 버전이 릴리즈 되었다. JUnit4의 단점을 개선하기 위해 등장한 JUnit5는 자바 8+ 모던 프로그래밍으로 개발이 되었고, 3개의 컴포넌트로 구성된다. 이 글에서는 JUnit5 의 기본 개념에 대해서 간략하게 정리하고, 스프링에서 어떻게 사용하는지 간단하게 정리한다.
이 글의 모든 내용은 아래 공식 레퍼런스를 참고하였다.
샘플 코드는 아래 링크를 참고하였다.
https://github.com/junit-team/junit5-samples
JUnit5는 3개의 컴포넌트로 구성되는데 아래와 같다.
JUnit Vintage : 기존 버전(JUnit3, JUnit4)을 위한 테스트 엔진을 제공한다.
Junit Jupiter : 새로운 프로그래밍 모델(JUnit5)을 위한 테스트 엔진을 제공한다.
Junit Platform : 테스트 엔진 인터페이스를 정의한다. (?)
JUnit5는 JUnit4와 동일한 라이프사이클을 사용한다. 단, 일부 어노테이션 이름이 변경되었다.
@BeforeAll : 모든 테스트 실행 전 최초 한번 실행 (JUnit4에서의 @BeforeClass)
@BeforeEach : 테스트 실행할 때마다 테스트 전에 실행 (JUnit4에서의 @Before)
@Test : 테스트 실행 (JUnit4에서의 @Test )
@AfterEach : 테스트 종료할 때마다 테스트 이후 실행 (JUnit4에서의 @After)
@AfterAll : 모든 테스트 종료 후 마지막 실행 (JUnit4에서의 @AfterClass)
@Disabled : 테스트를 수행하지 않고 패스 (JUnit4에서 @Ignore)
@DiplayName : 테스트 이름 설정
샘플 코드
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@DisplayName("라이프사이클 설명을 위한 클래스")
public class LifeCycle {
@BeforeAll
static void initializeBeforeAll() {
System.out.println("initializeBeforeAll...");
}
@BeforeEach
void initializeBeforeEach() {
System.out.println("initializeBeforeEach...");
}
@Test
@DisplayName("@Test를 사용하는 첫 번째 테스트")
void firstTest() {
System.out.println("첫번째 테스트...");
assertTrue(true);
}
@Test
@DisplayName("@Test를 사용하는 두 번째 테스트")
void secondTest() {
assumeTrue(true);
System.out.println("두 번째 테스트...");
assertNotEquals(1, 2, "");
}
@Test
@Disabled
@DisplayName("@Disabled를 사용하는 스킵 테스트")
void disabledTest() {
System.out.println("disabled...");
}
@AfterEach
void afterEach() {
System.out.println("afterEach...");
}
@AfterAll
static void afterAll() {
System.out.println("afterAll...");
}
}
레퍼런스
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
설명은 쿨하게 생략한다.
샘플 코드
Person person = new Person("sieun", 1981);
@Test
void standardAssertions(){
assertEquals(7,7);
assertNotEquals(3,7);
assertTrue(3 < 7);
assertEquals("sieun",person.getName());
assertTrue(1985 > person.getBorn());
}
@Test
void groupedAssertions() {
assertAll("person",
() -> assertEquals("sieun", person.getName()),
() -> assertEquals("1981", person.getBorn().toString())
);
}
@Test
void dependentAssertions() {
assertAll("properties",
() -> {
String name = person.getName();
assertNotNull(name);
assertAll("name",
() -> assertTrue(name.startsWith("s")),
() -> assertTrue(name.endsWith("n"))
);
},
() -> {
Person.Gender gender = person.getGender();
assertNull(gender);
},
() -> {
Person.Gender gender = irene.getGender();
assertNotNull(gender);
assertAll("아이린",
() -> assertEquals("irene", irene.getName()),
() -> assertEquals(Person.Gender.F, irene.getGender())
);
}
);
}
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
testCompile('org.junit.jupiter:junit-jupiter-api:5.2.0')
testRuntime('org.junit.jupiter:junit-jupiter-engine:5.2.0')
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test'){
exclude group: 'junit'
}
testCompile('org.junit.jupiter:junit-jupiter-api:5.2.0')
testRuntime('org.junit.jupiter:junit-jupiter-engine:5.2.0')
}
이 글을 작성하는 현재, 스프링부트 최신 버전은 2.0.4.RELEASE 이다. spring-boot-start-test 에는 JUnit 4.12 버전이 디펜던시가 되어있기 때문에, 4점대 버전을 exclude 하고 5.X 버전을 추가하였다.
스프링부트가 아닌 경우에는??
@ExtendWith 어노테이션으로 스프링 또는 Mokito 라이브러리를 확장해서 사용할 수 있고, @ContextConfiguration 어노테이션으로 컨텍스트 설정을 할 수 있다.
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { MySpringApplication.class })
class SimpleSpringTest {
...
참고 레퍼런스
스프링 부트2.0에서는 일반적인 스프링에서와 마찬가지로 @ExtendWith 어노테이션을 사용하지만, @ContextConfiguration 대신에 @SpringBootTest 을 사용한다. @SpringBootTest 어노테이션은 애플리케이션 컨텍스트를 실행해준다.
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
public CoffeeComponent coffeeComponent;
@Test
@DisplayName("첫번째 테스트")
public void test(){
assertEquals("커피를주문한다.", coffeeComponent.getCoffeeMessage());
}
}
생략..
사실 이 글은 별 내용이 없는 글이다. JUnit5를 사용하던, JUnit4를 사용하던, 모키토를 사용하던... 라이브러리는 테스트를 지원할 뿐이다. 좋은 테스트코드를 위해서는, 라이브러리 사용 유무보다는 개발자의 역량이 더 중요하다. 필자는 테스트 코드를 어느 수준까지 작성해야하는지 그 기준을 정하는게 아직 어렵다. 테스트코드도 전략이 필요한데 아직은 어려운것 같다. (개발을 오래했지만, 습관이 안되어있으니... 테스트코드 만큼은 초보개발자나 다름없다..반성하고)
사실 이번에, "테스트 코드 작성하는 법"에 대해서 글을 쓰고 싶었는데, 실력이 많이 부족해서 일단 포기했다. 필자가 테스트 코드를 어느정도 잘 작성하기 시작했다고 판단되면, 그때 다시 글을 꼭 작성하도록 하겠다.
약속!!