JUnit은 Java 및 Kotlin 생태계에서 단위 테스트를 위한 표준 프레임워크로, Android 개발에서 격리된 구성 요소 수준에서 비즈니스 로직을 검증하는 데 사용됩니다. 이 프레임워크는 어노테이션, assertion 메서드 및 Test Runner 세트를 제공하여 테스트를 자동으로 검색하고 실행합니다. JUnit.org에 따르면, 이 라이브러리는 JVM 생태계에서 단위 테스트를 위한 가장 인기 있는 솔루션으로 남아 있으며, 70% 이상의 Java 프로젝트가 어떤 버전이든 JUnit을 사용합니다.
주요 포인트
JUnit은 Java 및 Kotlin에서 반복 가능한 단위 테스트를 작성하고 실행하기 위한 오픈 소스 프레임워크입니다. xUnit 생태계의 일부로, Kent Beck과 Erich Gamma의 아키텍처를 기반으로 한 프레임워크 제품군이며 Android Studio 및 IntelliJ IDEA의 표준 테스트 도구입니다.
JUnit의 주요 임무는 코드의 작은 부분(메서드, 클래스)을 분리하고 통제된 조건에서 동작을 검증하는 것입니다. 테스트는 어노테이션이 있는 일반 Java/Kotlin 클래스로 작성되며, 프레임워크가 테스트 검색, 라이프사이클 관리 및 실행 통계 수집을 처리합니다.
JUnit의 첫 번째 버전은 1997년에 등장했으며, 테스트 주도 개발(TDD) 방식을 대중화하여 개발 접근 방식을 근본적으로 변화시켰습니다. 오늘날 JUnit 5(Jupiter)는 최신 버전으로, Java 8+ 및 확장을 지원하는 모듈식 아키텍처로 완전히 재설계되었습니다.
JUnit 실행 메커니즘은 Test Runner 패턴을 기반으로 하며, classpath를 스캔하고 @Test 어노테이션이 있는 메서드를 찾아 테스트 클래스 인스턴스를 만들고 특정 순서로 메서드를 실행합니다. Test Runner는 라이프사이클을 관리합니다: BeforeAll → BeforeEach → Test → AfterEach → AfterAll.
JUnit은 각 테스트 메서드를 별도의 클래스 인스턴스로 래핑하여 테스트 간 격리를 보장합니다. 즉, 클래스 필드는 서로 다른 @Test 메서드 간에 상태를 유지하지 않으며, 각 테스트는 깨끗한 객체로 시작됩니다.
JUnit에서 가장 간단한 테스트는 @Test 어노테이션이 있는 메서드로, 테스트 대상 코드를 호출하고 assertion을 사용하여 결과를 검증합니다. assertion이 실패하면 테스트는 실패한 것으로 간주되고 JUnit이 오류를 보고합니다.
@Test
void additionShouldReturnCorrectSum() {
var calculator = new Calculator();
var result = calculator.add(2, 3);
assertEquals(5, result);
}
Test Runner는 자동으로 이러한 메서드를 찾아 실행하고 결과를 보고합니다. 메서드가 예외를 던지지 않으면 테스트는 통과(green)하고, assertion이 실패하면 테스트는 실패(red)합니다.
JUnit의 어노테이션은 테스트 코드를 언제, 어떻게 실행할지 정의합니다. JUnit 5에서 어노테이션은 org.junit.jupiter.api 패키지에 있으며 데이터 준비, 테스트 실행, 리소스 정리의 모든 단계를 다룹니다.
| 어노테이션 | 목적 | 실행 시점 |
|---|---|---|
| @Test | 테스트 메서드 표시 | 호출당 한 번 |
| @BeforeEach | 각 테스트 전 설정 | 각 @Test 전 |
| @BeforeAll | 클래스 일회용 초기화 | 모든 테스트 전 한 번 |
| @AfterEach | 각 테스트 후 정리 | 각 @Test 후 |
| @AfterAll | 클래스 일회용 종료 | 모든 테스트 후 한 번 |
| @DisplayName | 사람이 읽을 수 있는 테스트 이름 | 장식 |
적절한 라이프사이클을 가진 완전한 테스트 클래스를 고려해 보겠습니다. setUp 메서드는 각 테스트 전에 새로운 Calculator 인스턴스를 만들고, tearDown은 리소스를 해제합니다(예: 파일 디스크립터 또는 데이터베이스 연결 닫기).
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@Test
void subtractionShouldReturnCorrectResult() {
int result = calculator.subtract(10, 4);
assertEquals(6, result);
}
@AfterEach
void tearDown() {
calculator.reset();
}
}
Assertions은 실제 결과를 예상 결과와 비교하고 일치하지 않으면 예외를 던지는 정적 메서드입니다. JUnit 5는 org.junit.jupiter.api.Assertions 클래스를 제공하며, 단순한 값 비교부터 타임아웃 확인 및 그룹화된 assertions까지 다양한 검증 시나리오를 위한 25개 이상의 메서드가 있습니다.
assertThrows 메서드는 특히 주목할 만합니다. 코드가 0으로 나누기, null 전달, 한도 초과와 같은 오류 상황을 올바르게 처리하는지 검증할 수 있습니다.
@Test
void divisionByZeroShouldThrowException() {
Calculator calc = new Calculator();
ArithmeticException exception = assertThrows(
ArithmeticException.class,
() -> calc.divide(10, 0)
);
assertEquals("Cannot divide by zero", exception.getMessage());
}
매개변수화된 테스트는 동일한 테스트 메서드를 다양한 인수 세트로 실행할 수 있게 합니다. JUnit 5에서는 @ParameterizedTest 어노테이션을 데이터 소스(@ValueSource, @CsvSource, @MethodSource 또는 @EnumSource)와 결합하여 사용합니다.
이 접근 방식은 코드 중복을 근본적으로 줄입니다. 다른 값에 대해 10개의 동일한 테스트를 작성하는 대신 하나의 매개변수화된 메서드를 작성합니다. Google Testing Blog에 따르면, 매개변수화는 커버리지를 잃지 않으면서 테스트 코드 양을 40~60% 줄입니다.
@CsvSource 어노테이션은 쉼표로 구분된 여러 행을 테스트에 전달합니다. 각 행은 하나의 테스트 실행에 해당합니다. JUnit은 자동으로 문자열 값을 필요한 타입(int, long, String 등)으로 변환합니다.
@ParameterizedTest
@CsvSource({
"1, 1, 2",
"2, 3, 5",
"10, 20, 30",
"-1, 1, 0"
})
void additionWithMultipleInputs(int a, int b, int expected) {
assertEquals(expected, a + b);
}
입력 데이터가 단순한 숫자나 문자열보다 복잡한 경우 @MethodSource를 사용합니다. 이는 인수의 Stream을 반환하는 정적 메서드를 참조합니다. 예를 들어, DAO 계층 테스트를 위한 Arguments의 Stream 또는 User 객체의 Stream 등이 있습니다.
Android 개발에서 JUnit은 AndroidX Test와 함께 사용되며, Activity, Content Provider 및 기타 Android 구성 요소를 테스트하기 위한 확장을 제공합니다. JUnit은 에뮬레이터 없이 JVM에서 실행되는 단위 테스트를 담당하고, AndroidX Test는 기기에서 계측 테스트를 담당합니다.
Android 프로젝트에 JUnit 5를 추가하려면 모듈 수준의 build.gradle에 org.junit.jupiter:junit-jupiter 종속성을 포함하기만 하면 됩니다. AGP Gradle 플러그인은 표준 Test Runner를 통해 JVM에서 JUnit 5 테스트 실행을 지원합니다.
// build.gradle.kts
android {
testOptions {
unitTests.isIncludeAndroidResources = true
}
}
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.11.0")
testImplementation("androidx.test:core-ktx:1.6.1")
}
실제로 JUnit은 Android 컨텍스트나 에뮬레이터가 필요하지 않은 ViewModel 및 리포지토리 계층을 테스트하는 데 가장 자주 사용됩니다. 이러한 테스트는 밀리초 단위로 실행되며 시간 낭비 없이 수백 번 실행할 수 있습니다.
JUnit 5(Jupiter)는 단순한 새 버전이 아니라 세 가지 모듈로 분할된 완전히 재설계된 플랫폼입니다: JUnit Platform(JVM에서 테스트 실행), JUnit Jupiter(테스트 작성을 위한 API), JUnit Vintage(JUnit 4와의 하위 호환성). 이 모듈성을 통해 다양한 Test Engine(예: Kotlin용 Spek 또는 TestNG)을 연결할 수 있습니다.
| 특징 | JUnit 4 | JUnit 5 |
|---|---|---|
| 패키지 | org.junit | org.junit.jupiter |
| 기본 어노테이션 | @Test(junit.framework) | @Test(org.junit.jupiter.api) |
| Before/After | @Before, @After, @BeforeClass | @BeforeEach, @AfterEach, @BeforeAll |
| 매개변수화 | @RunWith(Parameterized.class) | @ParameterizedTest + @ValueSource |
| 확장 | @Rule, @ClassRule | @ExtendWith, 더 유연한 API |
| Java 최소 | Java 5 | Java 8+ |
JUnit 4에서 JUnit 5로 마이그레이션하려면 모든 테스트를 다시 작성할 필요가 없습니다. JUnit Vintage Engine을 추가하기만 하면 기존 테스트가 계속 작동합니다. 새 테스트는 확장, 람다 assertions 및 내장된 매개변수화 지원을 활용하기 위해 JUnit 5로 작성하는 것이 좋습니다.
자주 묻는 질문
JUnit은 테스트를 작성하고 실행하기 위한 프레임워크이고, Mockito는 모의 객체를 만들기 위한 라이브러리입니다. 이들은 함께 사용됩니다: JUnit은 테스트 실행을 관리하고 Mockito는 테스트 대상 클래스의 종속성을 대체합니다.
네, JUnit 5는 Android 프로젝트와 완전히 호환됩니다. 단위 테스트의 경우 build.gradle에 junit-jupiter 종속성을 추가하기만 하면 됩니다. 계측 테스트는 AndroidX Test Runner를 통해 계속 작동합니다.
최소한 메서드 앞에 하나의 @Test 어노테이션만 있으면 충분합니다. 초기화 및 정리를 위해 @BeforeEach와 @AfterEach가 권장되지만 필수는 아닙니다.
Test Runner는 classpath를 스캔하고 @Test 어노테이션이 있는 메서드를 찾아 테스트 클래스 인스턴스를 만들고 테스트를 실행하는 JUnit 구성 요소입니다. JUnit 5에서는 연결된 Test Engine과 함께 JUnit Platform이 이 역할을 수행합니다.
assertThrows(Class, Executable)를 사용하세요. 예상 예외 유형과 메서드 호출이 있는 람다를 받습니다. JUnit은 예외가 실제로 발생했는지 확인하고 추가 검사를 위해 반환합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.