사전 JUnit
구현체

JUnit

gabury1고친 사람 github-actions[bot]

JUnit 은 자바 코드를 검사하는 테스트를 짜고 돌리게 해 주는 도구입니다. 테스트로 쓸 메서드에 표시를 붙여 두면 JUnit 이 그 메서드를 찾아 하나씩 부릅니다. 다 돌고 나면 어느 테스트가 통과했고 어느 테스트가 깨졌는지 알려 줍니다.

쉽고 빠른 이해

JUnit 은 자바 코드가 기대한 대로 도는지 명령 한 번으로 확인하게 해 줍니다. 가격 계산기에 2만 원을 넣으면 1만 8천 원이 나와야 한다는 사실을 테스트로 적어 두면, 코드를 고칠 때마다 그 테스트를 다시 돌려 볼 수 있습니다.

이 도구가 없으면 확인할 때마다 실행용 코드를 따로 만들어 출력을 눈으로 봐야 합니다. 확인할 것이 수백 개로 늘면 그렇게 해서는 끝이 안 납니다. JUnit 에서는 나온 값을 기대한 값과 견주는 일을 테스트 코드가 사람 대신 합니다. 짜는 모양과 돌리는 방법도 하나로 정해 두어서 누가 돌려도 같은 테스트가 같은 방식으로 돕니다.

어떻게 도는가:

  1. 테스트로 쓸 메서드에 @Test 라는 표시를 붙입니다
  2. JUnit 이 그 표시가 붙은 메서드를 찾아 하나씩 부릅니다
  3. 기대한 값과 나온 값이 다르면 그 테스트를 실패로 적고 다음 테스트로 넘어갑니다
  4. 끝나면 통과한 수와 실패한 수, 실패한 까닭을 보여 줍니다

대가도 있습니다. 테스트 코드도 코드라서 본 코드가 바뀌면 같이 고쳐야 합니다. JUnit 은 테스트를 찾아 부르고 결과를 모으는 일만 합니다. 가짜 객체를 만들거나 데이터베이스를 띄우는 일은 다른 도구를 붙여서 합니다.

상세

JUnit 은 자바 코드를 검사하는 테스트를 짜는 틀입니다. 짜 놓은 테스트를 돌리는 테스트 러너이기도 합니다. 테스트 러너는 테스트를 찾아 차례로 부르고 결과를 모으는 프로그램입니다. 자바로 백엔드를 만들면 테스트는 대개 이 도구로 짭니다.

이름은 자바의 J 에 단위를 뜻하는 영어 unit 을 붙인 것입니다. 단위 테스트는 메서드나 클래스 하나처럼 작은 조각을 떼어 검사하는 테스트입니다. 이름은 단위 테스트에서 왔지만 여러 조각을 묶어 검사하는 통합 테스트도 JUnit 으로 짭니다.

JUnit 이 없으면 코드를 확인할 때마다 main 메서드 같은 실행용 코드를 따로 만들어야 합니다. 그리고 찍혀 나온 값을 사람이 눈으로 견줘야 합니다. 확인할 것이 늘수록 눈으로 견줄 것도 그만큼 늘어납니다.

JUnit 테스트에서는 기대한 값을 코드에 적어 두고, 나온 값과 견주는 일을 그 코드가 맡습니다. 통과와 실패는 JUnit 이 한데 모아 보여 줍니다. 테스트를 짜는 모양과 돌리는 방법도 하나로 정해 둡니다. 그래서 누가 돌려도 결과를 같은 기준으로 견줄 수 있습니다.

이 절은 테스트 하나를 짜는 모양에서 시작합니다. 이어서 JUnit 이 그 테스트를 어떻게 찾아 부르는지, 무엇을 실패로 치는지, 테스트 앞뒤의 준비와 정리를 어떻게 맡는지, 테스트를 무엇으로 돌리는지를 봅니다. 끝으로 JUnit 5 가 어떤 부분으로 나뉘어 있는지와 JUnit 이 맡지 않는 일을 봅니다.

테스트 하나의 모양

JUnit 테스트는 평범한 자바 메서드에 어노테이션을 붙여 만듭니다. 어노테이션은 @ 로 시작하는 표시입니다. 코드에 꼬리표처럼 붙여 두면 다른 프로그램이 그 꼬리표를 읽어 갑니다.

아래는 2만 원 이상 사면 2천 원을 깎아 주는 가격 계산기를 검사하는 테스트입니다.

Java
@Test
void 이만원이면_이천원_깎는다() {
    var calc = new PriceCalculator();
    int price = calc.apply(20000); // 18000
    assertEquals(18000, price);
}

@Test 가 붙은 메서드 하나가 테스트 하나입니다. 메서드 이름은 무엇을 검사하는지를 문장처럼 적습니다. 테스트가 깨지면 이 이름이 결과에 뜨기 때문입니다.

마지막 줄의 assertEquals 는 두 값을 견줍니다. 앞에 기대한 값을, 뒤에 나온 값을 적습니다. 이렇게 결과를 확인하는 한 줄을 단언이라고 부릅니다.

테스트를 찾아 부르는 방법

JUnit 은 테스트 클래스를 읽어 @Test 가 붙은 메서드를 골라냅니다. 프로그램이 도는 중에 클래스 안에 어떤 메서드가 있는지 읽어 내는 자바 기능을 씁니다. 이 기능을 리플렉션이라고 부릅니다. 그래서 테스트를 새로 짜도 목록에 따로 등록할 필요가 없습니다.

JUnit 은 기본으로 테스트 메서드를 하나 부를 때마다 테스트 클래스의 객체를 새로 만듭니다. 앞 테스트가 필드에 남긴 값이 뒤 테스트로 새지 않게 하려는 것입니다.

테스트가 도는 순서도 약속하지 않습니다. 파일에 적은 순서대로 돈다고 믿고 짜면 안 됩니다. 앞 테스트가 만든 데이터를 뒤 테스트가 쓰도록 짜면, 순서가 바뀌는 날 뒤 테스트가 깨집니다.

실패로 치는 경우

단언은 두 값이 다르면 예외를 던집니다. 예외는 자바에서 무언가 잘못됐다고 알리는 신호입니다. 예외가 나면 메서드는 거기서 빠져나옵니다. JUnit 은 테스트 메서드 밖으로 예외가 빠져나오면 그 테스트를 실패로 적습니다.

위 테스트에서 계산기가 할인을 빠뜨려 2만 원을 그대로 돌려줬다고 해 봅시다. assertEquals 가 예외를 던지고, JUnit 은 결과에 이런 줄을 남깁니다.

expected: <18000> but was: <20000>

기대한 값은 18000 인데 나온 값은 20000 이라는 뜻입니다. JUnit 은 이 테스트를 실패로 적고 다음 테스트로 넘어갑니다. 테스트 하나가 깨져도 나머지는 끝까지 돕니다.

거꾸로 예외가 나야 맞는 경우도 있습니다. 음수 금액을 넣으면 계산기가 거절해야 한다면 이렇게 적습니다.

Java
assertThrows(IllegalArgumentException.class,
        () -> calc.apply(-1));

assertThrows 는 괄호 안의 코드를 돌려 봅니다. 적어 둔 종류의 예외가 나야 통과입니다. 예외가 안 나면 이 테스트가 실패합니다.

준비와 정리를 맡는 어노테이션

테스트마다 같은 준비가 필요할 때가 많습니다. 계산기 객체를 만들거나 테스트용 데이터를 채우는 일입니다. 이런 준비를 메서드 하나에 모아 두고 어노테이션을 붙이면 JUnit 이 알맞은 때에 불러 줍니다.

어노테이션 언제 도나 흔한 쓰임
@BeforeEach 테스트 메서드마다 그 앞에서 검사할 객체를 새로 만든다
@AfterEach 테스트 메서드마다 그 뒤에서 테스트가 쓴 데이터를 지운다
@BeforeAll 테스트 클래스 전체에서 처음 한 번 테스트용 데이터베이스처럼 띄우는 데 오래 걸리는 것을 한 번만 띄운다
@AfterAll 테스트 클래스 전체에서 마지막 한 번 처음에 띄운 것을 내린다

@BeforeAll 은 테스트 클래스의 객체가 하나도 만들어지기 전에 돕니다. @AfterAll 은 마지막 객체까지 다 쓰고 난 뒤에 돕니다. 두 때 모두 부를 객체가 없어서, 이 둘을 붙이는 메서드는 객체 없이 부를 수 있는 static 메서드로 적습니다.

이 넷과 테스트 메서드가 도는 순서를 그리면 아래와 같습니다. 테스트 메서드 하나마다 가운데 네 칸이 한 바퀴 돕니다.

flowchart TD
    A["@BeforeAll · 처음 한 번"] --> B["테스트 클래스 객체를 새로 만든다"]
    B --> C["@BeforeEach"]
    C --> D["@Test 메서드"]
    D --> E["@AfterEach"]
    E --> F{"남은 테스트 메서드가 있나"}
    F -->|있다| B
    F -->|없다| G["@AfterAll · 마지막 한 번"]

테스트를 돌리는 도구

JUnit 테스트는 혼자 돌리는 일이 드뭅니다. 대개 빌드 도구가 코드를 컴파일한 다음 테스트 단계에서 JUnit 을 부릅니다. Maven 과 Gradle 이 모두 이 단계를 갖고 있습니다. 테스트가 하나라도 실패하면 빌드가 거기서 멈춥니다.

통합 개발 환경에서도 돌립니다. 통합 개발 환경은 코드 편집과 빌드, 실행을 한 프로그램에 모은 개발 도구입니다. 테스트 메서드 옆의 실행 버튼을 누르면 그 테스트 하나만 돌려 볼 수 있습니다.

같은 테스트가 지속적 통합 서버에서도 돕니다. 지속적 통합은 코드가 올라올 때마다 서버가 빌드와 테스트를 자동으로 돌리는 관행입니다. 여기서 테스트가 깨지면 방금 올린 변경이 무언가를 망가뜨렸다는 신호가 됩니다.

이렇게 쓰는 데는 대가가 따릅니다. 테스트는 빌드마다 돌기 때문에 테스트가 늘면 빌드도 그만큼 길어집니다.

Platform · Jupiter · Vintage 세 부분

JUnit 4 까지는 라이브러리 하나였습니다. JUnit 5 부터는 세 부분으로 나뉩니다. 테스트를 돌리는 쪽과 테스트를 짜는 방식을 떼어 놓은 것입니다.

이렇게 나누면서 테스트 엔진이라는 부분이 생겼습니다. 테스트 엔진은 한 가지 방식으로 짠 테스트를 알아보고 실행하는 부분입니다. JUnit 5 에서 테스트를 짜는 방식의 이름이 Jupiter 입니다. 앞에서 본 @Test · assertEquals 가 모두 Jupiter 에 속합니다.

부분 맡는 일
JUnit Platform 테스트를 돌리는 바탕. Maven · Gradle 같은 빌드 도구와 통합 개발 환경이 여기에 붙는다
JUnit Jupiter @Test · assertEquals 같은 짜는 방식과, 그렇게 짠 테스트를 돌리는 엔진
JUnit Vintage JUnit 4 로 짠 테스트를 Platform 위에서 돌리는 엔진

Platform 은 엔진이 무엇이든 같은 방식으로 부릅니다. 그래서 JUnit 4 로 짠 테스트와 Jupiter 로 짠 테스트를 한 번에 돌릴 수 있습니다. 프로젝트 의존성에 junit-jupiter 라는 이름이 보이면 Jupiter 로 테스트를 짠다는 뜻입니다.

JUnit 4 와 Jupiter 는 @Test 를 두는 패키지도 다릅니다. JUnit 4 의 @Test 는 org.junit 에, Jupiter 의 @Test 는 org.junit.jupiter.api 에 있습니다. 두 @Test 가 어느 엔진에 잡히는지 그리면 아래와 같습니다.

flowchart TD
    A["빌드 도구 · 통합 개발 환경"] --> P["JUnit Platform"]
    P --> J["Jupiter 엔진"]
    P --> V["Vintage 엔진"]
    J --> T1["org.junit.jupiter.api 의 @Test"]
    V --> T2["org.junit 의 @Test"]

Vintage 엔진을 넣지 않으면 그림의 Vintage 갈래가 통째로 빠집니다. 그 상태로 org.junit 의 @Test 를 가져다 쓰면 그 테스트를 알아볼 엔진이 없습니다. 테스트가 실패하는 것이 아니라 아예 돌지 않습니다.

준비와 정리를 맡는 어노테이션 이름도 바뀌었습니다. JUnit 4 의 @Before · @After 가 Jupiter 에서는 @BeforeEach · @AfterEach 입니다.

JUnit 이 맡지 않는 일

JUnit 이 맡는 일은 테스트를 돌려 통과인지 실패인지 가리는 데서 끝납니다. 테스트를 짜다 보면 그 밖의 일이 필요해집니다. 그 일은 다른 도구가 맡습니다.

필요한 일 흔히 붙이는 도구
데이터베이스나 외부 서버를 부르는 객체를 가짜로 바꿔 끼우기 Mockito
단언을 문장처럼 읽히게 쓰기 AssertJ
진짜 데이터베이스를 테스트용으로 잠깐 띄우기 Testcontainers

진짜 대신 끼우는 가짜를 테스트 더블이라고 부릅니다. 결제 서버를 진짜로 부르면 테스트가 느려지고 돌릴 때마다 결과가 달라질 수 있습니다. 그래서 정해진 답만 돌려주는 가짜를 끼워 검사할 조각만 떼어 냅니다.

이런 도구들은 JUnit 의 확장 기능으로 붙습니다. 테스트 클래스에 @ExtendWith 라는 어노테이션을 달고 붙일 도구를 적으면, JUnit 이 테스트 앞뒤에 그 도구를 끼워 부릅니다. 스프링의 테스트 지원도 이 방식으로 JUnit 에 붙습니다.

관련 항목

JUnit 으로 짜는 테스트의 종류

단위 테스트 · 통합 테스트 · 회귀 테스트 · 인수 테스트 · 매개변수화 테스트

JUnit 테스트를 이루는 구성 요소

테스트 케이스 · 테스트 스위트 · 단언 · 테스트 픽스처 · 어노테이션 · 예외

JUnit 5 를 이루는 모듈과 엔진

JUnit Platform · JUnit Jupiter · JUnit Vintage · 테스트 엔진 · JUnit 4

JUnit 테스트에 끼워 넣는 가짜 객체

테스트 더블 · 목 객체 · 스텁 · 페이크 객체

JUnit 에 붙여 쓰는 테스트 도구

Mockito · AssertJ · Hamcrest · Testcontainers · WireMock · Spring Boot Test

JUnit 테스트를 돌리는 빌드 도구와 개발 환경

Maven · Gradle · Maven Surefire · 통합 개발 환경 · IntelliJ IDEA · 지속적 통합

JUnit 과 같은 역할을 맡는 테스트 도구

xUnit · TestNG · unittest · pytest · Jest · NUnit · Kotest

JUnit 테스트를 흔드는 문제

불안정한 테스트 · 테스트 격리 · 테스트 순서 의존 · AssertionError

JUnit 이 속하는 상위 분류

테스트 러너 · 테스트 프레임워크 · 테스트 자동화 · QA와 테스트 · Java · 리플렉션

다른 이름: JUnit 5 · 제이유닛