작은 테스트
고친 사람 github-actions[bot]
작은 테스트는 테스트를 프로그램 하나 안에서만 돌려서 빠르고 늘 같은 결과를 내게 합니다. 디스크나 네트워크처럼 느리고 흔들리는 것에는 손대지 못하게 막습니다. 그래서 코드를 고칠 때마다 마음 놓고 돌립니다. 테스트 가운데 가장 많이 두는 것도 이것입니다.
쉽고 빠른 이해
무슨 일을 하나 — 테스트가 쓸 수 있는 것을 프로그램 하나 안으로 묶어 둡니다. 주문을 저장하는 코드를 확인할 때 데이터베이스 대신 메모리에 만든 가짜 저장소를 붙여 돌리는 식입니다.
왜 이렇게 하나 — 테스트를 느리게 하고 결과를 흔드는 것은 대개 디스크와 네트워크와 기다림입니다. 이 셋을 처음부터 못 쓰게 하면 테스트가 계산만 하다 끝납니다.
어떻게 도나
- 테스트와 확인할 코드가 같은 프로그램 안에서 돕니다
- 디스크나 네트워크가 필요한 곳에는 가짜를 끼웁니다
- 금방 끝나고 몇 번을 돌려도 같은 결과가 나옵니다
대가 — 가짜를 만드는 품이 듭니다. 가짜는 만든 사람이 짐작한 대로만 움직입니다. 그래서 서버끼리 이어 봐야 드러나는 잘못은 작은 테스트로 못 잡습니다.
상세
이 절은 테스트를 크기로 나누는 잣대부터 봅니다. 그다음 작은 테스트에 걸린 제한과 그 까닭을 봅니다. 주문을 저장하는 코드 하나를 가지고 작은 테스트가 무엇을 쓰고 무엇을 못 쓰는지 보입니다.
시험장을 떠올려 봅시다. 책상 위에 놓인 것만 쓰라고 하면 누가 언제 치르든 조건이 같습니다. 밖에 나가 자료를 찾아오라고 하면 도서관이 붐비는 날엔 오래 걸립니다. 문이 닫힌 날엔 아예 못 끝냅니다. 작은 테스트는 책상 위의 것만 쓰는 시험입니다.
테스트 크기라는 잣대
테스트 크기는 테스트가 돌 때 무엇을 쓰느냐로 테스트를 나눕니다. 코드 줄 수와는 상관이 없습니다. 이 잣대를 따로 두는 까닭은 테스트가 얼마나 빨리 끝나는지, 돌릴 때마다 같은 결과를 내는지가 쓰는 것에 달려 있기 때문입니다.
잣대는 세 칸입니다. 프로세스는 실행 중인 프로그램 하나입니다. 아래 표는 칸마다 테스트가 도는 곳을 모았습니다. 이 세 칸은 구글이 자기 테스트를 나누려고 세운 틀입니다.
작은 테스트는 가장 좁은 칸입니다. 테스트와 확인할 코드가 한 프로세스 안에서 함께 돕니다. 서버를 따로 띄워 두고 다른 프로세스의 테스트가 그 서버에 붙는 꼴은 작은 테스트가 될 수 없습니다.
작은 테스트에 걸린 제한
프로세스 하나라는 조건 위에 제한이 몇 가지 더 붙습니다. 이 소절은 제한을 하나씩 풀고 나서 표로 모읍니다.
첫째, 언어에 따라서는 스레드 하나로까지 좁힙니다. 스레드는 한 프로세스 안에서 따로 도는 실행 흐름입니다. 흐름이 하나뿐이면 어느 줄이 먼저 도는지가 늘 같습니다.
둘째, 잠들 수 없습니다. 잠든다는 것은 Thread.sleep(1000) 처럼 정해 둔 시간 동안 멈춰 기다리는 일입니다. 기다린 만큼 테스트가 길어집니다. 기다린 시간이 모자라면 테스트가 깨집니다.
셋째, 막혀 기다리는 호출을 할 수 없습니다. 이런 호출을 블로킹 호출이라고 부릅니다. 답이 올 때까지 프로그램이 그 줄에서 멈추기 때문입니다.
블로킹 호출의 대표가 입출력입니다. 입출력은 프로그램이 디스크나 네트워크처럼 바깥과 데이터를 주고받는 일입니다. 그래서 작은 테스트는 디스크도 네트워크도 못 씁니다.
| 제한 | 막는 것 |
|---|---|
| 프로세스 하나 | 다른 프로세스에 띄운 서버에 붙기 |
| 스레드 하나 · 언어에 따라 | 실행 순서가 매번 달라지는 것 |
| 잠들기 금지 | 정해 둔 시간만큼 멈춰 기다리기 |
| 블로킹 호출 금지 | 디스크 읽기와 쓰기 · 네트워크 호출 |
제한을 두는 까닭
제한은 전부 한 방향을 향합니다. 테스트를 느리게 하는 것과 결과를 흔드는 것을 처음부터 못 쓰게 합니다. 디스크와 네트워크와 기다림이 그 둘의 주된 원천입니다.
속도부터 봅니다. 프로세스 하나에서 돌고 멈춰 기다리지 않는 테스트는 계산만 합니다. 그래서 컴퓨터가 계산할 수 있는 만큼 빨리 끝납니다.
다음은 흔들림입니다. 결정성은 같은 입력에 늘 같은 결과를 내는 성질입니다. 네트워크가 잠깐 막히거나 디스크가 바쁘면 코드를 하나도 안 바꿔도 테스트가 깨집니다. 작은 테스트는 그런 바깥 사정을 아예 안 만나므로 결정성을 지키기 쉽습니다.
돌릴 때마다 붙었다 떨어졌다 하는 테스트를 불안정한 테스트라고 부릅니다. 이런 테스트가 쌓이면 깨져도 아무도 안 믿게 됩니다. 작은 테스트는 이런 테스트가 생길 길을 처음부터 줄입니다.
디스크와 네트워크 대신 쓰는 것
주문 서비스가 데이터베이스에 주문을 저장한다고 해 봅시다. 데이터베이스는 디스크와 네트워크를 쓰므로 작은 테스트에 들일 수 없습니다. 이럴 때는 데이터베이스를 같은 프로세스 안에서 도는 대용품으로 바꿉니다.
테스트 더블은 테스트에서 진짜 대신 쓰는 가짜입니다. 확인할 코드가 기대는 디스크나 네트워크 쪽 부품을 이것으로 바꿔 끼우면, 테스트가 그 부품의 기다림과 흔들림을 물려받지 않습니다.
테스트 더블 가운데 페이크는 진짜처럼 동작하지만 훨씬 단순하게 만든 가짜입니다. 데이터베이스 대신 메모리에 주문을 담아 두는 가짜 저장소가 흔한 예입니다.
아래 코드는 주문 데이터를 두 방식으로 다룹니다. 위쪽 묶음은 디스크의 파일을 읽어서 작은 테스트가 못 됩니다. 아래쪽 묶음은 메모리의 가짜 저장소만 써서 작은 테스트가 됩니다.
// 디스크를 읽는다: 작은 테스트가 못 된다
var text = Files.readString(Path.of("orders.txt"));
// 메모리의 가짜 저장소: 작은 테스트
var repo = new InMemoryOrderRepository();
repo.save(new Order(1, 3000));
repo.find(1).price(); // 3000
아래쪽 묶음은 디스크를 한 번도 건드리지 않습니다. 저장한 주문을 다시 꺼내면 메모리에 담아 둔 값이 나옵니다. 파일이 있는지 없는지에 기대지 않습니다. 그래서 돌릴 때마다 같은 값이 나옵니다.
파일을 다루는 코드도 같은 방법을 씁니다. 파일 시스템은 디스크에 파일을 이름으로 담고 찾는 방식입니다. 작은 테스트에서는 이것을 메모리 안에서 흉내 낸 가짜로 바꿔 씁니다.
한 프로세스에 담긴 한 벌
이 소절은 작은 테스트 한 벌이 무엇을 안에 두고 무엇을 밖에 두는지 그림으로 봅니다.
flowchart TD
subgraph P["프로세스 하나"]
T["테스트"] --> S["주문 서비스"]
S --> F["가짜 저장소"]
end
S -.->|"막힘"| D["디스크"]
S -.->|"막힘"| N["다른 프로세스의 서버"]
테스트와 주문 서비스와 가짜 저장소가 모두 한 프로세스 안에 있습니다. 점선 둘은 작은 테스트가 넘지 못하는 선입니다. 주문 서비스가 디스크로 가는 길도, 다른 프로세스의 서버로 가는 길도 막혀 있습니다.
작은 테스트와 단위 테스트
작은 테스트와 자주 헷갈리는 말이 단위 테스트입니다. 단위 테스트는 함수나 클래스 하나를 떼어 확인하는 테스트입니다. 둘은 서로 다른 잣대에서 나온 말입니다.
단위 테스트가 속한 잣대는 테스트 범위입니다. 범위는 테스트가 어느 코드를 확인하느냐를 봅니다. 크기는 테스트가 돌 때 무엇을 쓰느냐를 봅니다.
두 잣대는 대개 함께 움직입니다. 범위가 좁은 테스트는 작은 테스트인 일이 많습니다. 그래도 늘 같이 가지는 않습니다.
클래스 하나만 확인하더라도 같은 머신에 진짜 데이터베이스를 띄워 붙이면 프로세스가 둘이 됩니다. 범위는 좁지만 크기는 중간입니다. 반대로 주문 · 재고 · 결제를 한 프로세스 안에서 엮고 바깥 부품을 전부 가짜로 바꾸면, 범위는 넓지만 크기는 작습니다.
작은 테스트를 고르는 때
같은 것을 확인할 수 있다면 범위와 상관없이 더 작은 칸을 고르기를 권합니다.
작은 테스트는 금방 끝나서 코드를 고칠 때마다 돌릴 수 있습니다. 고친 사람이 무엇을 깨뜨렸는지 바로 압니다. 그래서 작은 테스트를 가장 많이 둡니다. 큰 테스트는 가장 적게 둡니다.
개수를 이렇게 쌓으면 아래가 넓은 삼각형이 됩니다. 이 모양을 테스트 피라미드라고 부릅니다.
작은 테스트로 못 잡는 것도 있습니다. 가짜는 만든 사람이 짐작한 대로만 움직이기 때문입니다. 서버 주소가 틀린 것처럼 서버끼리 이어 봐야 드러나는 잘못은 중간 테스트와 큰 테스트가 맡습니다.
관련 항목
작은 테스트와 같은 크기 잣대로 나뉘는 테스트
테스트 크기 · 중간 테스트 · 큰 테스트 · 허미틱 테스트
작은 테스트와 다른 축인 범위로 나뉘는 테스트
테스트 범위 · 좁은 범위 테스트 · 중간 범위 테스트 · 넓은 범위 테스트 · 단위 테스트 · 통합 테스트 · 종단 간 테스트
작은 테스트가 디스크와 네트워크 대신 끼우는 가짜
테스트 더블 · 페이크 · 목 객체 · 스텁 · 인메모리 데이터베이스
작은 테스트가 막아 두는 기다림과 흔들림의 원천
입출력 · 블로킹 · 네트워크 · 파일 시스템 · 스레드 · 경쟁 상태
작은 테스트가 지키는 성질과 그 반대 증상
작은 테스트의 개수를 정하는 배분 모양
테스트 피라미드 · 테스팅 트로피 · 아이스크림 콘 안티패턴
작은 테스트를 자주 돌리는 도구와 단계
다른 이름: small test · small tests · 소형 테스트