Junit5를 알아보자
❒ Description
나의 경우 JUnit5를 회사에서 처음으로 사용하게 되었는데, "왜 JUnit5를 사용하지?" 에대해서 생각해보지 않았다.
여태까지 그냥 너도 나도 JUnit5를 사용해서 나도 사용해왔다. 하지만 알고 쓰는 것과 모르고 쓰는 것엔 굉장히 큰 차이가
있기 때문에 이번 기회에 JUnit5 정확히 뭔지, 왜 쓰는지, 해당 프레임워크를 사용하여 취할 수 있는 이점이 무엇인지,
다른 테스팅 프레임워크에는 뭐가 있는지 공부하고 알아보려고 한다. 더 나아가 직접 설정까지 하는 시간을 가져볼 것이다.
❒ JUnit5 ?
JUnit5는 이전 버전과는 다른 구성을 가지고 있다. 구성은 다음과 같다.
JUnit5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform
JUnit Platform은 아래의 역할을 수행한다.
• JVM에서 테스트 프레임워크를 시작하기 위한 기반 역할
• 테스트 프레임워크를 개발하기 위한 TestEngine API를 정의
• 커맨드 라인에서 실행할 수 있게 하기 위한 Console Launcher 제공
• 사용자 지정 테스트를 실행할 수 있는 JUnit Platform Suite Engine 제공
JUnit Jupiter
JUnit Jupiter는 JUnit5에서 테스트를 작성하고, 확장 기능을 추가하기 위한 '프로그래밍 모델'과
'확장 모델'의 조합이다. Jupiter 하위 프로젝트는 플랫폼에서 주피터 기반 테스트를 실행하기 위한
TestEngine을 제공한다.
여기서 언급된 '확장 기능'은 `@ExtendWith(~~)`을 사용할 때를 생각하면 된다.
JUnit Vintage
JUnit Vintage는 플랫폼에서 JUnit3 및 JUnit4 기반 테스트를 실행하기 위한 TestEngine을 제공한다.
참고로 JUnit 4.12 이상이 있어야 한다.
결과적으로 JUnit Vintage가 있기 때문에 이전 버전으로 작성된 기존 테스트들을 JUnit 5 환경에서도 계속
사용할 수 있는 것이다.
❒ JUnit5 configuration
dependencies {
testImplementation(platform("org.junit:junit-bom:5.10.3"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
}
기본적으로 프로젝트를 생성하면 org.junit:junit-bom을 사용하게 된다. BOM은 위에서 알아본 JUnit5를
구성하고 있는 각각의 모듈을 통합하여 관리해주는 역할을 한다.
물론 개별적으로 의존성 주입도 할 수 있다.
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.3")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.3")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.10.3")
}
하지만 개별적으로 의존성 주입을 하게 되면 분명 나중에 버전관리가 힘들 것 같다...
특별한 경우가 아니라면 BOM을 사용하여 버전을 일관성 있게 관리하는게 좋을 것 같다.
추가적으로 필요에 따라 추가 설정을 할 수 있다. 예를 들면 로깅 관련된 설정이 필요하면 아래와 같이 추가 설정을 해줘야
한다. 공식문서를 보니 로깅 외에도 다양한 설정이 있으니깐, 필요할 때 공식문서를 참고해서 작업하면 될 듯 하다.
test {
systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager")
}
❒ AssertJ configuration
Junit5 공식문서에 보다 다양한 기능과 좋은 성능을 위해 AssertJ를 사용하라고 추천해주고 있다.
AssertJ는 어설션(assertion) 라이브러리로, 플루언트(fluent) 스타일의 어설션을 제공하여 테스트 코드를 읽기
쉽게 만들어 준다. 뿐만 아니라 BDD 스타일의 테스트 코드 작성도 지원해준다.
플루언트(fluent) 스타일
‣ 메서드 호출이 연쇄적으로 이루어져 마치 자연어를 읽는 것처럼 코드가 가독성을 갖도록 하는 프로그래밍 스타일
dependencies {
testImplementation("org.assertj:assertj-core:3.25.3")
}
추가적으로 JUnit5와 자주 사용되는 third-party assertion 라이브러리로는 Java Harmcrest, Truth 등이 있다고 한다.
import Member;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertSame;
public class TestSample {
@Test
void testSomething() {
Member member = new Member("gilbert");
// JUnit5
assertSame(member.name, "gilbert");
// AssertJ
assertThat(member.name)
.as("name validation")
.isEqualTo("gilbert");
}
}
이렇게 AssertJ를 쓰면 체이닝 형식으로 보다 더 가독성 좋은 코드를 작성할 수 있다.
하지만 JUnit5과 AssertJ 모두 Assertions 클래스를 가지고 있다. 사용자가 잘 구분해서 사용하면 문제는 없겠지만
여러명이 개발하다 보면 Assertions 클래스를 혼용해서 사용할 여지가 충분하다고 생각한다.
대표적인 예시로 첨에 wewoot 프로젝트를 참여했을 때 이미 두 Assertions 클래스를 혼용해서 쓰고 있었다.
그래서 나는 이런 생각을 했다.
사용하지 않을 Assertions를 의존성 주입할 때 Exclude 시키면 되는 것 아닌가?
아쉽게도 Gradle 공식문서에서는 특정 클래스만을 제외하는 방법은 찾지 못했다. 패키지 단위로 exclude 하는
것 같다. 그나마 할 수 있는 건 Intellij IDEA에 warning 등록 정도가 아닐까 싶다.
Intellij에서 warning을 등록하는 방법을 알아냈다! 참고: jetbrain issue
1. Inspections > JVM language > illegal package dependencies > `Configure dependency rules`
2. `...` 클릭 후 원하는 클래스 찾아서 include
3. (Optioanl) `Configure dependency rules` 위쪽에 Serverify custom.
이렇게 하면 아래와 같이 경고를 보여줄 수 있다.
이 설정도 몇 가지 단점이 있다고 생각한다.
- 단순히 경로르 보여주는 것일 뿐, 코드상 문제가 없다면 정상 컴파일
- 사용자마다 Intellij 설정을 해야한다는 번거로움
❒ Mockito configuration
테스트 코드를 작성할 때 '가짜 객체'를 많이 사용하는데 Mockito이 부분을 도와주는 대표적인 프레임워크이다.
dependencies {
testImplementation("org.mockito:mockito-core:5.11.0")
}
❒ 다양한 테스팅 프레임워크
조사해 보니 JUnit 말고도 다양한 프레임워크가 사용되고 있다.
• 2024년도 기준 Top10 프레임워크를 장단점과 함께 정리 되어있는 사이트
상황에 맞게 적절한 테스팅 프레임워크를 사용면 될 것으로 보인다.
❒ Conclusion
정리해보자면 JUnit5는 복합 모듈로 구성되며, 앞선 버전과는 다르게 JUnit Platform 위에서 테스트 코드를
실행한다. 또한 이전 버전에 없던 새로운 기능도 제공하며, 기존에 작성된 테스트 호환성도 지원한다.
추후에 JUnit6,7...이 출시되더라도 별도의 source code migration 없이 잘 작동하지 않을까 싶다.
약간 JVM이랑 비슷한 철학으로 JUnit5를 만든 것 같다는 생각이 든다. JVM 덕분에 OS에 종속되지 않고 자바
애플리케이션을 실행할 수 있듯이, JUnit5를 사용하면 JUnit Platform 덕분에 테스트 코드가 특정 버전에
종속되지 않고 실행될 수 있기 때문이다.
물론 새로운 기능이 추가되서 JUnit5를 사용하는 것도 있겠지만, 멀리 봤을 때 버전에 종속적이지 않은 테스트
코드를 쓸 수 있다는 점이 JUnit5를 쓰는 가장 큰 이유가 아닐까 조심스레 생각해본다.