← Java 강의 목록으로
모던 자바
모던 자바 · 선수: 이전 단원

22. JUnit 테스트

코드를 "잘 동작한다" 고 말하려면 **검증** 이 필요합니다. JUnit 5 는 Java 의 사실상 표준 테스트 프레임워크로, 짧고 명확한 어노테이션 기반 API 를 제공합니다.

Java모던함수형JUnit 테스트
소요 시간
약 1.5~2시간
난이도
📊 중급-고급
선수 조건
🎯 이전 단원 또는 동등 지식
결과물
코드를 "잘 동작한다" 고 말하려면 **검증** 이 필요합니다. JUnit 5 는 Java 의 사실상 표준 테스트 프레임워크로, 짧고 명확한 어노테이션 기반 API 를 제공합니다.

이 강의에서 배우는 것

  • 1Maven 프로젝트의 `src/test/java` 위치를 안다
  • 2`@Test`, `@BeforeEach`, `@AfterEach`, `@DisplayName` 을 사용한다
  • 3`assertEquals` / `assertTrue` / `assertThrows` 의 차이를 안다
  • 4`mvn test` 로 테스트를 실행한다

소개

코드를 "잘 동작한다" 고 말하려면 **검증** 이 필요합니다. JUnit 5 는 Java 의 사실상 표준 테스트 프레임워크로, 짧고 명확한 어노테이션 기반 API 를 제공합니다.

핵심 개념

1) Maven 의존성

xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.5</version>
    <scope>test</scope>
</dependency>

`scope=test` 는 운영 코드에는 포함되지 않게 합니다.

2) 기본 테스트

java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    @Test
    void add는_두_수의_합을_반환() {
        assertEquals(5, new Calculator().add(2, 3));
    }
}

3) 라이프사이클

java
@BeforeEach void setUp() { ... }    // 각 @Test 전에 실행
@AfterEach  void tearDown() { ... } // 각 @Test 후에 실행
@BeforeAll  static void all() { ... }  // 클래스 전체 시작 시 1회
@AfterAll   static void clean() { ... } // 클래스 전체 끝에 1회

4) 예외 테스트

java
@Test
void zero_division() {
    assertThrows(ArithmeticException.class,
        () -> new Calculator().divide(10, 0));
}

5) `mvn test`

bash
cd 22_JUnit_테스트
mvn test

JUnit 5 가 자동으로 발견·실행해 결과를 출력합니다.

핵심 예제

예제 1 — `Calculator.java` : 운영 코드

java
package com.codingnow.lecture.modern22;

public class Calculator {
    public int add(int a, int b) { return a + b; }
    public int subtract(int a, int b) { return a - b; }
    public int divide(int a, int b) {
        if (b == 0) throw new ArithmeticException("/ zero");
        return a / b;
    }
}

예제 2 — `CalculatorTest.java` : 가장 기본 테스트

java
package com.codingnow.lecture.modern22;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    Calculator calc = new Calculator();

    @Test
    @DisplayName("add 는 두 수의 합을 반환")
    void add는_합을_반환() {
        assertEquals(5, calc.add(2, 3));
        assertEquals(0, calc.add(-1, 1));
    }

    @Test
    void divide_by_zero() {
        assertThrows(ArithmeticException.class, () -> calc.divide(10, 0));
    }
}

**`mvn test` 실행 결과 (일부)**

text
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

예제 3 — `FixtureTest.java` : 라이프사이클

java
package com.codingnow.lecture.modern22;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class FixtureTest {
    Calculator calc;

    @BeforeEach
    void setUp() {
        calc = new Calculator();
        System.out.println("(setUp)");
    }

    @AfterEach
    void tearDown() {
        System.out.println("(tearDown)");
    }

    @Test
    void test_add() { assertEquals(7, calc.add(3, 4)); }

    @Test
    void test_sub() { assertEquals(1, calc.subtract(4, 3)); }
}

콘솔에 `(setUp)` 과 `(tearDown)` 이 각 테스트마다 출력됩니다.

예제 4 — `AssertionsTest.java` : 다양한 assertion

java
package com.codingnow.lecture.modern22;

import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;

class AssertionsTest {
    @Test
    void variety() {
        assertTrue(2 + 2 == 4);
        assertFalse("hi".isEmpty());
        assertNotNull("");
        assertNull(null);
        assertArrayEquals(new int[]{1, 2, 3}, new int[]{1, 2, 3});
        assertIterableEquals(List.of("a"), List.of("a"));
    }
}

전체 예제 코드 (src/)

src/main/java/com/codingnow/lecture/modern22/Calculator.java

java
package com.codingnow.lecture.modern22;

/** 단순 계산기. */
public class Calculator {
    public int add(int a, int b) { return a + b; }
    public int subtract(int a, int b) { return a - b; }
    public int divide(int a, int b) {
        if (b == 0) throw new ArithmeticException("/ zero");
        return a / b;
    }
}

src/test/java/com/codingnow/lecture/modern22/AssertionsTest.java

java
package com.codingnow.lecture.modern22;

import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

class AssertionsTest {
    @Test
    void variety() {
        assertTrue(2 + 2 == 4);
        assertFalse("hi".isEmpty());
        assertNotNull("");
        assertNull(null);
        assertArrayEquals(new int[]{1, 2, 3}, new int[]{1, 2, 3});
        assertIterableEquals(List.of("a"), List.of("a"));
    }
}

src/test/java/com/codingnow/lecture/modern22/CalculatorTest.java

java
package com.codingnow.lecture.modern22;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class CalculatorTest {
    Calculator calc = new Calculator();

    @Test
    @DisplayName("add 는 두 수의 합을 반환")
    void add는_합을_반환() {
        assertEquals(5, calc.add(2, 3));
        assertEquals(0, calc.add(-1, 1));
    }

    @Test
    void subtract_정상() {
        assertEquals(2, calc.subtract(5, 3));
    }

    @Test
    void divide_by_zero() {
        assertThrows(ArithmeticException.class, () -> calc.divide(10, 0));
    }
}

src/test/java/com/codingnow/lecture/modern22/FixtureTest.java

java
package com.codingnow.lecture.modern22;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class FixtureTest {
    Calculator calc;

    @BeforeEach
    void setUp() {
        calc = new Calculator();
        System.out.println("(setUp)");
    }

    @AfterEach
    void tearDown() {
        System.out.println("(tearDown)");
    }

    @Test
    void test_add() { assertEquals(7, calc.add(3, 4)); }

    @Test
    void test_sub() { assertEquals(1, calc.subtract(4, 3)); }
}

자주 하는 실수

  1. JUnit 의존성을 `compile` 스코프로 추가 → 운영 jar 가 비대해짐
  2. `@Test` 메서드를 `public` 으로만 작성 (5 에선 `package-private` 또는 `public` 모두 OK, `private` 만 안 됨)
  3. 테스트끼리 의존성 (한 테스트 결과가 다른 테스트에 영향)
  4. `assertEquals(actual, expected)` 처럼 인자 순서를 바꿈 (정답이 먼저)
  5. 예외 발생을 직접 try/catch 로 잡고 만족 → `assertThrows` 사용

정리

  • JUnit 5 + Maven 으로 자동화된 검증을 시작
  • `@Test` 메서드는 작고, 한 가지만 검증
  • `@BeforeEach` 로 공통 초기화 분리
  • `assertThrows` 로 예외 흐름도 검증

과제

# 과제 - 22. JUnit 테스트

## 문제 — `StringUtils` 검증

  • 위치: `answer/` 안의 Maven 프로젝트 구조
  • 핵심 개념: TDD, `assertEquals`, `assertTrue`

요구사항

  • `StringUtils.reverse(String)` — 문자열 뒤집기
  • `StringUtils.isPalindrome(String)` — 회문 여부 (대소문자 무시, 공백 무시)
  • 위 두 메서드에 대해 각각 정상/엣지 케이스 테스트를 작성합니다.

예상 동작

  • `mvn test` 실행 시 모든 테스트 통과.

힌트

  • `StringUtils` 는 `src/main/java/.../StringUtils.java`
  • 테스트는 `src/test/java/.../StringUtilsTest.java`

## 정답 확인 [`answer/`](./answer/) 폴더의 정답과 비교해 보세요.

정답 코드 (homework/answer/)

answer/pom.xml

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.codingnow</groupId>
    <artifactId>lecture-modern22-hw</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.5</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.5</version>
            </plugin>
        </plugins>
    </build>
</project>

answer/src/main/java/com/codingnow/lecture/modern22hw/StringUtils.java

java
package com.codingnow.lecture.modern22hw;

/** 문자열 유틸. */
public class StringUtils {
    /** 문자열을 뒤집어 반환. */
    public static String reverse(String s) {
        if (s == null) return null;
        return new StringBuilder(s).reverse().toString();
    }

    /** 회문 여부 (대소문자 무시, 공백 무시). */
    public static boolean isPalindrome(String s) {
        if (s == null) return false;
        String t = s.replace(" ", "").toLowerCase();
        return t.equals(reverse(t));
    }
}

answer/src/test/java/com/codingnow/lecture/modern22hw/StringUtilsTest.java

java
package com.codingnow.lecture.modern22hw;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

class StringUtilsTest {

    @Test
    void reverse_정상() {
        assertEquals("avaJ", StringUtils.reverse("Java"));
    }

    @Test
    void reverse_빈문자열() {
        assertEquals("", StringUtils.reverse(""));
    }

    @Test
    void reverse_null_은_null() {
        assertNull(StringUtils.reverse(null));
    }

    @Test
    void palindrome_단어() {
        assertTrue(StringUtils.isPalindrome("level"));
    }

    @Test
    void palindrome_공백_대소문자_무시() {
        assertTrue(StringUtils.isPalindrome("A man a plan a canal Panama"));
    }

    @Test
    void palindrome_아닌_경우() {
        assertFalse(StringUtils.isPalindrome("Hello"));
    }
}

직접 해 보기

bash
cd 05_모던_자바/22_JUnit_테스트
mvn test

다음 단원

[23_Spring_Boot_시작](../../06_Spring_Boot/23_Spring_Boot_시작/) — Spring Initializr 로 첫 Spring Boot 앱을 만듭니다.

예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗