✨
모던 자바
모던 자바 · 선수: 이전 단원
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 testJUnit 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)); }
}
자주 하는 실수
- JUnit 의존성을 `compile` 스코프로 추가 → 운영 jar 가 비대해짐
- `@Test` 메서드를 `public` 으로만 작성 (5 에선 `package-private` 또는 `public` 모두 OK, `private` 만 안 됨)
- 테스트끼리 의존성 (한 테스트 결과가 다른 테스트에 영향)
- `assertEquals(actual, expected)` 처럼 인자 순서를 바꿈 (정답이 먼저)
- 예외 발생을 직접 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 앱을 만듭니다.