테스트 클래스
고친 사람 github-actions[bot]
테스트 클래스는 한 대상을 확인하는 테스트들을 한곳에 모아 줍니다. 클래스 안의 메서드 하나하나가 테스트 하나입니다. 테스트마다 필요한 준비와 정리도 이 클래스에 한 번만 적어 두고 함께 씁니다.
쉽고 빠른 이해
무슨 일을 하나 — 한 대상에 대한 테스트를 한 클래스에 모읍니다. 장바구니 클래스를 확인하는 테스트는 전부 CartTest 라는 클래스 하나에 들어갑니다.
왜 이렇게 하나 — 테스트가 수백 개가 되면 어느 테스트가 무엇을 보는지 찾기 어렵습니다. 대상별로 묶어 두면 찾기 쉽습니다. 그 묶음만 골라 돌릴 수도 있습니다. 테스트마다 되풀이하던 준비 코드도 한 번만 적으면 됩니다.
어떻게 도나
- 테스트를 돌리는 프로그램(테스트 러너)이 테스트 클래스를 찾아냅니다
- 테스트 메서드 하나를 돌릴 때마다 클래스의 객체를 새로 만들고 준비 코드를 먼저 부릅니다
- 테스트 메서드를 부르고 정리 코드로 뒷정리를 합니다
대가 — 묶는 기준을 잘못 잡으면 한 클래스가 한없이 커집니다. 객체는 매번 새로 만들어도, 모든 객체가 함께 쓰는 필드(자바의 static 필드)는 하나뿐입니다. 여기에 값을 두면 한 테스트의 결과가 다른 테스트에 번질 수 있습니다.
상세
자동차 정비소의 점검표 한 장에는 차 한 대의 점검 항목이 모여 있습니다. 브레이크, 전조등, 타이어가 한 줄씩 적혀 있습니다. 「항목마다 시동을 끈 상태에서 시작한다」 같은 공통 준비는 표 맨 위에 한 번만 적혀 있습니다.
테스트 클래스는 이 점검표에 해당합니다. 한 대상을 확인하는 테스트 메서드를 모아 둔 클래스입니다. 장바구니 클래스 Cart 를 확인하는 테스트라면 CartTest 라는 클래스 하나에 모읍니다. 대상 클래스 이름 뒤에 Test 를 붙여 짓는 것이 흔한 관례입니다.
테스트 메서드 하나는 대개 테스트 케이스 하나입니다. 테스트 케이스는 코드가 맞게 도는지 한 가지씩 판정하는 확인입니다. 넣을 값과 나와야 할 값을 미리 정해 둡니다. 테스트 클래스는 이런 케이스 여럿을 담는 그릇입니다.
테스트 클래스는 사람이 직접 부르지 않습니다. 테스트 러너가 부릅니다. 러너는 테스트를 찾아 하나씩 돌리고 결과를 모아 보여 주는 프로그램입니다. 러너는 테스트 클래스를 찾아 그 안의 테스트 메서드를 하나씩 부릅니다.
모아 두면 좋아지는 세 가지
테스트를 클래스로 묶는 까닭은 셋입니다. 찾기, 골라 돌리기, 준비 코드 나눠 쓰기입니다.
첫째는 찾기입니다. 테스트가 수백 개면 장바구니 테스트가 어디 있는지부터 헤맵니다. 대상마다 테스트 클래스가 하나면 Cart 의 테스트는 CartTest 에서 찾으면 됩니다.
둘째는 골라 돌리기입니다. 테스트 클래스 하나는 그대로 테스트 스위트 하나가 됩니다. 스위트는 함께 돌리려고 묶어 둔 테스트의 모음입니다. 장바구니 코드를 고치는 동안에는 CartTest 만 돌려 몇 초 만에 확인할 수 있습니다.
셋째는 준비 코드입니다. 장바구니 테스트는 거의 다 빈 장바구니를 하나 만들고 시작합니다. 이 준비를 테스트마다 적으면 같은 줄이 수십 번 되풀이됩니다. 테스트 클래스에는 이런 준비를 한 번만 적어 두는 방법이 있습니다.
테스트 클래스 안에 드는 것
테스트 클래스에는 테스트 메서드 말고도 몇 가지가 더 들어갑니다.
| 넣는 것 | 하는 일 |
|---|---|
| 테스트 메서드 | 확인 하나를 맡는다 |
| 준비 메서드 | 테스트 메서드마다 그 앞에 돈다 |
| 정리 메서드 | 테스트 메서드마다 그 뒤에 돈다 |
| 클래스 준비 · 클래스 정리 | 클래스 전체에서 처음과 끝에 한 번씩 돈다 |
| 필드 | 준비 메서드가 만든 값을 테스트 메서드에 건넨다 |
준비 메서드는 테스트 메서드가 돌기 직전에 불리는 메서드입니다. 빈 장바구니를 만드는 일처럼 모든 테스트가 필요로 하는 준비를 여기 적습니다. 테스트가 기대는 이런 준비물을 테스트 픽스처라고 부릅니다.
정리 메서드는 테스트 메서드가 끝난 뒤에 불립니다. 테스트가 만든 임시 파일을 지우는 일처럼 뒷정리를 맡습니다. 테스트가 실패해도 정리 메서드는 불립니다. 그래야 한 테스트의 흔적이 다음 테스트에 남지 않습니다.
클래스 준비와 클래스 정리는 클래스 전체에서 한 번씩만 돕니다. 테스트용 데이터베이스처럼 띄우는 데 오래 걸리는 것을 여기서 한 번만 띄우고 내립니다. 준비, 정리, 클래스 준비, 클래스 정리처럼 테스트의 앞뒤에 끼어드는 메서드를 한데 생명주기 메서드라고 부릅니다.
코드로 본 테스트 클래스
자바의 테스트 도구 JUnit 으로 장바구니 테스트 클래스를 짜 봅니다. JUnit 은 메서드 위에 붙이는 표시로 그 메서드의 역할을 알립니다. 이런 표시를 어노테이션이라고 합니다.
class CartTest {
Cart cart;
@BeforeEach
void setUp() {
cart = new Cart();
}
@Test
void 빈_장바구니는_0원() {
int sum = cart.total(); // 0
assertEquals(0, sum); // 통과
}
@Test
void 담으면_합계가_는다() {
cart.add(20000);
int sum = cart.total(); // 20000
assertEquals(20000, sum); // 통과
}
}
@BeforeEach 가 붙은 setUp 은 준비 메서드입니다. 빈 장바구니를 만들어 필드 cart 에 넣습니다. @Test 가 붙은 두 테스트 메서드는 그 필드를 꺼내 씁니다.
줄 오른쪽 주석은 그 줄이 내는 값입니다. assertEquals 는 나온 값이 기대한 값과 같은지 맞춰 보는 단언입니다.
두 테스트 메서드에는 장바구니를 만드는 줄이 없습니다. 준비를 setUp 이 한 번 적어 맡았기 때문입니다. 테스트가 스무 개로 늘어도 준비 코드는 이 세 줄뿐입니다.
테스트 메서드마다 새 객체
위 코드에서 둘째 테스트는 장바구니에 2만 원을 담습니다. 이 테스트가 먼저 돌아도 첫째 테스트는 0원을 얻습니다. 러너가 테스트 메서드를 하나 부를 때마다 테스트 클래스의 객체를 새로 만들기 때문입니다.
객체가 새로 생기면 필드도 새로 생깁니다. 그 위에서 준비 메서드가 다시 돌아 빈 장바구니를 새로 채웁니다. 앞 테스트가 필드에 남긴 값은 버려진 객체와 함께 사라집니다. JUnit 과 파이썬의 unittest 가 기본으로 이렇게 돕니다.
러너가 테스트 클래스 하나를 도는 순서를 그림으로 봅니다.
flowchart TD
A["클래스 준비 · 처음 한 번"] --> B
subgraph M["테스트 메서드마다 되풀이"]
B["테스트 클래스 객체를 새로 만든다"] --> C["준비 메서드"]
C --> D["테스트 메서드"]
D --> E["정리 메서드"]
end
E --> F["클래스 정리 · 마지막 한 번"]
가운데 네모 묶음이 테스트 메서드 수만큼 되풀이됩니다. 테스트 메서드가 셋이면 객체도 셋이 만들어집니다. 클래스 준비와 클래스 정리는 몇 개든 한 번씩입니다.
이렇게 떼어 두는 까닭은 테스트끼리 서로 기대지 않게 하려는 것입니다. 앞 테스트가 남긴 값에 기대는 테스트는 순서가 바뀌면 깨집니다. 이런 상태를 테스트 순서 의존성이라고 합니다.
새 객체로도 못 막는 공유
모든 객체가 함께 쓰는 값은 새 객체를 만들어도 안 사라집니다. 자바의 static 필드가 그렇습니다. 이런 필드는 객체가 아니라 클래스에 하나만 있습니다.
static 필드에 장바구니를 두면 모든 테스트 메서드가 같은 장바구니를 씁니다. 둘째 테스트가 2만 원을 담은 뒤 첫째 테스트가 돌면 0원이 아니라 2만 원을 얻습니다. 첫째 테스트는 순서에 따라 통과하기도 하고 실패하기도 합니다.
클래스 준비는 이런 공유 필드를 채우는 일이 많습니다. 띄우는 데 오래 걸리는 것을 한 번만 띄우려면 테스트끼리 나눠 써야 하기 때문입니다. 그래서 클래스 준비로 만든 것은 테스트가 고치지 않고 읽기만 하게 짭니다.
러너가 테스트 클래스를 알아보는 표시
러너는 코드 안의 수많은 클래스 가운데 테스트 클래스만 골라냅니다. 무엇을 보고 고르는지는 도구마다 다릅니다. 널리 쓰는 도구 셋을 표로 견줍니다.
| 도구 | 테스트 클래스로 알아보는 표시 | 준비 · 정리 메서드 |
|---|---|---|
| JUnit 5 | @Test 가 붙은 메서드가 있다 |
@BeforeEach · @AfterEach |
| unittest | unittest.TestCase 를 물려받았다 |
setUp · tearDown |
| pytest | 이름이 Test 로 시작하고 생성자가 없다 |
setup_method · teardown_method |
unittest 는 상속으로 알아봅니다. 상속은 이미 있는 클래스의 기능을 물려받아 새 클래스를 짓는 방법입니다. TestCase 를 물려받는 순간 준비와 정리를 부르는 순서, 단언 메서드가 함께 딸려 옵니다.
JUnit 과 pytest 는 물려받을 클래스가 따로 없습니다. JUnit 은 어노테이션을, pytest 는 이름을 봅니다. 그래서 두 도구의 테스트 클래스는 평범한 클래스처럼 생겼습니다.
C# 의 테스트 도구 NUnit 은 테스트 클래스에 [TestFixture] 라는 표시를 붙입니다. 그래서 NUnit 을 쓰는 곳에서는 테스트 클래스를 「픽스처」라고 부르기도 합니다. 앞에서 본 준비물이라는 뜻의 픽스처와 이름이 겹칩니다. 글을 읽을 때 어느 쪽 뜻인지 살펴야 합니다.
테스트 클래스를 나누는 기준
가장 흔한 기준은 대상 클래스 하나에 테스트 클래스 하나입니다. Cart 에는 CartTest, Order 에는 OrderTest 를 둡니다. 대상 코드와 테스트 코드가 짝을 이뤄 찾기가 쉽습니다.
대상 클래스가 크면 테스트 클래스도 따라 커집니다. 이때는 기능별로 나눕니다. 할인 계산 테스트와 배송비 테스트를 다른 클래스로 떼는 식입니다.
준비가 다른 무리가 생겨도 나눕니다. 빈 장바구니로 시작하는 테스트와 상품이 가득 찬 장바구니로 시작하는 테스트가 섞이면 준비 메서드 하나로 둘을 다 맞출 수 없습니다. 준비가 같은 테스트끼리 클래스를 따로 두면 준비 메서드가 다시 하나로 줄어듭니다.
테스트 클래스를 쓰지 않는 도구
테스트 클래스는 클래스로 코드를 짜는 언어의 테스트 도구에서 나온 단위입니다. JUnit, NUnit 처럼 이름이 Unit 으로 끝나는 이런 도구 무리를 xUnit 이라고 부릅니다. 클래스로 묶는 방식은 이 무리가 함께 따르는 틀입니다.
클래스가 꼭 필요하지 않은 도구도 있습니다. pytest 는 test_ 로 시작하는 함수만 적어도 테스트로 알아봅니다. 묶음은 파일이 대신합니다. 이 도구에서 테스트 클래스는 준비를 나눠 쓰고 싶을 때 고르는 선택지입니다.
Go 언어에는 클래스가 없습니다. 테스트는 이름이 _test.go 로 끝나는 파일에 Test 로 시작하는 함수로 적습니다. 이 언어에서는 파일과 패키지가 테스트 클래스의 몫을 맡습니다.
관련 항목
테스트 클래스 안에 드는 구성 요소
테스트 메서드 · 테스트 케이스 · 생명주기 메서드 · 테스트 픽스처 · 단언
테스트 클래스를 묶고 돌리는 도구
테스트 스위트 · 테스트 러너 · 테스트 탐색 · 중첩 테스트 클래스
테스트 클래스를 제공하는 테스트 프레임워크
xUnit · JUnit · unittest · pytest · NUnit · TestNG · xUnit.net
테스트 클래스를 짜는 데 쓰는 언어 기능
어노테이션 · 상속 · 리플렉션 · 정적 필드
테스트 클래스를 흔들리게 만드는 문제
테스트 순서 의존성 · 공유 픽스처 · 불안정한 테스트 · 깨지기 쉬운 테스트
테스트 클래스에 담기는 테스트 종류
단위 테스트 · 통합 테스트 · 매개변수화 테스트 · 회귀 테스트
테스트 클래스가 속하는 상위 분류
QA와 테스트 · 소프트웨어 테스트 · 자동 테스트 · 테스트 프레임워크
다른 이름: test class · 테스트클래스 · testcase class