테스트 크기
고친 사람 github-actions[bot]
테스트 크기는 테스트가 돌면서 디스크 · 네트워크 같은 바깥 것을 어디까지 쓸 수 있는지에 선을 그어 테스트를 나눕니다. 선 안쪽이 좁을수록 테스트가 빨리 끝나고 결과가 덜 흔들립니다. 코드가 몇 줄인지와는 상관이 없습니다.
쉽고 빠른 이해
무슨 일을 하나 — 테스트마다 바깥 것을 어디까지 쓸 수 있는지 선을 정해 작은 테스트 · 중간 테스트 · 큰 테스트로 나눕니다. 메모리 안에서만 계산하면 작은 테스트입니다. 같은 머신에 데이터베이스를 띄워 붙이면 중간 테스트입니다. 다른 머신의 서버를 부르면 큰 테스트입니다.
왜 이렇게 하나 — 테스트를 느리게 하고 결과를 흔드는 것은 대개 디스크와 네트워크와 기다림입니다. 이것들을 쓰는지로 나누면 이름만 보고도 테스트가 얼마나 빠르고 믿을 만한지 압니다.
어떻게 도나
- 테스트마다 크기를 붙입니다
- 크기마다 쓸 수 있는 바깥 것이 정해져 있습니다
- 테스트가 그 선을 넘으면 테스트 도구가 실패로 처리합니다
대가 — 작은 테스트로 만들려면 데이터베이스 같은 부품을 가짜로 바꿔 끼워야 합니다. 가짜는 만든 사람이 짐작한 대로만 움직여서, 진짜 부품끼리 이어 봐야 드러나는 잘못은 작은 테스트로 못 잡습니다.
상세
이 절은 테스트를 크기로 나누는 까닭부터 봅니다. 이어서 세 칸이 각각 무엇을 허락하는지 봅니다. 주문을 저장하는 코드 하나를 세 크기로 테스트해 보며 칸이 어떻게 갈리는지 보입니다. 끝으로 크기와 자주 섞이는 다른 잣대인 범위와 견줍니다.
심부름을 떠올려 봅시다. 방 안에서 물건을 가져오는 일과 집 안 다른 방에서 가져오는 일과 집 밖 가게에 다녀오는 일은 걸리는 시간이 다릅니다. 집 밖으로 나가면 길이 막히거나 가게가 문을 닫아 빈손으로 돌아올 수도 있습니다. 무엇을 가져오느냐보다 어디까지 다녀오느냐가 시간과 성공을 가릅니다.
크기를 따로 재는 까닭
테스트 묶음에 가장 바라는 성질은 둘입니다. 빨리 끝나는 것과 돌릴 때마다 같은 결과를 내는 것입니다. 빨라야 코드를 고칠 때마다 돌릴 수 있습니다.
뒤의 성질을 결정성이라고 부릅니다. 같은 코드로 몇 번을 돌려도 결과가 같은 성질입니다.
결정성이 깨지면 코드를 하나도 안 바꿨는데 테스트가 통과했다 실패했다 합니다. 이런 테스트를 불안정한 테스트라고 부릅니다. 이런 테스트가 쌓이면 깨져도 아무도 안 믿게 됩니다.
테스트를 느리게 하고 흔드는 것은 주로 테스트 바깥에서 옵니다. 디스크가 바쁘면 읽기가 늦어집니다. 네트워크가 잠깐 막히면 요청이 실패합니다. 정해 둔 시간만큼 멈춰 기다리는 코드는 기다린 만큼 느려집니다. 다른 작업이 끝나기를 1초 기다리기로 했는데 그 작업이 1.2초 걸리면 테스트가 깨집니다.
그래서 테스트 크기는 코드 양이 아니라 이런 바깥 것에 손대는지로 테스트를 나눕니다. 크기 이름만 보면 그 테스트가 얼마나 빠르고 얼마나 흔들릴지 짐작할 수 있습니다. 단위 테스트나 통합 테스트 같은 이름은 무엇을 확인하는지는 말하지만, 그 테스트가 디스크를 쓰는지는 알려 주지 않습니다.
세 칸과 그 선
칸은 셋입니다. 작은 테스트는 프로세스 하나 안에서 돕니다. 프로세스는 실행 중인 프로그램 하나입니다. 중간 테스트는 머신 한 대 안에서 돕니다. 큰 테스트는 여러 머신에 걸쳐 돌아도 됩니다.
작은 테스트에는 제한이 더 붙습니다. 되도록 스레드 하나로까지 좁힙니다. 스레드는 한 프로세스 안에서 따로 도는 실행 흐름입니다. 흐름이 하나뿐이면 어느 줄이 먼저 도는지가 늘 같습니다.
작은 테스트는 잠들 수도 없습니다. 잠든다는 것은 정해 둔 시간 동안 멈춰 기다리는 일입니다.
작은 테스트는 블로킹 호출도 못 합니다. 블로킹 호출은 답이 올 때까지 프로그램이 그 줄에서 멈추는 호출입니다. 디스크 읽기와 네트워크 호출이 대표입니다. 작은 테스트가 디스크와 네트워크를 못 쓰는 것도 이 때문입니다.
중간 테스트는 이 제한을 풉니다. 프로세스를 여럿 띄우고 스레드를 쓰고 블로킹 호출을 해도 됩니다. 남은 제한은 네트워크 하나입니다. localhost 로만 요청을 보낼 수 있습니다. localhost 는 자기 머신을 가리키는 이름이라서, 거기로 보낸 요청은 머신 밖으로 나가지 않습니다.
큰 테스트는 그 마지막 제한까지 풉니다. 테스트와 확인할 시스템이 여러 머신에 흩어져도 됩니다. 아래 표는 세 칸이 허락하는 것을 모았습니다.
| 할 수 있는 것 | 작은 | 중간 | 큰 |
|---|---|---|---|
| 프로세스 여럿 띄우기 | ✗ | ✓ | ✓ |
| 스레드 여럿 쓰기 | 되도록 ✗ | ✓ | ✓ |
| 잠들기 · 블로킹 호출 | ✗ | ✓ | ✓ |
| 디스크 읽고 쓰기 | ✗ | ✓ | ✓ |
| localhost 로 네트워크 호출 | ✗ | ✓ | ✓ |
| 다른 머신으로 네트워크 호출 | ✗ | ✗ | ✓ |
표를 왼쪽에서 오른쪽으로 읽으면 작은 데서 큰 데로 갈수록 ✓가 늘어납니다. 중간 테스트는 머신 한 대 안의 것을 다 풉니다. 큰 테스트는 머신 밖까지 풉니다. 세 칸은 서로 안에 들어가는 모양이라 그림으로 보면 이렇습니다.
flowchart TD
subgraph L["큰 테스트 · 여러 머신"]
subgraph M["중간 테스트 · 머신 한 대"]
subgraph S["작은 테스트 · 프로세스 하나"]
T["테스트와 확인할 코드"]
end
DB["같은 머신의 데이터베이스"]
end
R["다른 머신의 서버"]
end
작은 테스트는 가장 안쪽 상자 밖으로 못 나갑니다. 중간 테스트는 같은 머신의 데이터베이스까지 닿습니다. 큰 테스트는 다른 머신의 서버까지 닿습니다. 앞의 심부름에 견주면 방 안이 프로세스 하나이고 집이 머신 한 대입니다.
주문 저장 코드 한 벌을 세 크기로
주문 서비스가 저장소에 주문을 저장한다고 해 봅시다. 확인하려는 것은 주문 서비스 하나입니다. 이 소절은 저장소를 무엇으로 끼우느냐만 바꿔서 크기가 어떻게 달라지는지 봅니다.
// 저장소만 바꿔 끼운 세 벌
new OrderService(new MemoryRepo()) // 작은
new OrderService(db("localhost")) // 중간
new OrderService(db("10.0.3.7")) // 큰
첫 줄의 MemoryRepo 는 주문을 메모리에 담아 두는 저장소입니다. 이렇게 테스트에서 진짜 대신 끼우는 가짜를 테스트 더블이라고 부릅니다. 디스크도 네트워크도 안 쓰니 작은 테스트입니다.
테스트 더블에도 여러 갈래가 있습니다. MemoryRepo 처럼 진짜처럼 동작하지만 단순하게 만든 것을 페이크라고 부릅니다.
둘째 줄은 같은 머신에 띄운 진짜 데이터베이스에 붙습니다. 데이터베이스는 따로 도는 프로세스입니다. 디스크도 쓰므로 작은 칸을 벗어납니다. 요청이 머신 밖으로 안 나가니 중간 테스트입니다.
셋째 줄의 10.0.3.7 은 다른 머신의 IP 주소입니다. IP(Internet Protocol) 주소는 네트워크에서 머신 한 대를 가리키는 번호입니다. 요청이 머신 밖으로 나가므로 큰 테스트입니다.
세 줄에서 테스트가 확인하는 대상은 늘 주문 서비스 하나입니다. 바뀐 것은 테스트가 손대는 바깥 것뿐입니다. 크기는 무엇을 확인하느냐가 아니라 확인하면서 무엇을 쓰느냐로 정해집니다.
크기를 지키게 하는 테스트 도구
크기는 이름표로 끝나지 않습니다. 테스트를 정의하는 곳에 size = "small" 처럼 크기 값을 적어 두는 식으로 크기를 붙입니다. 적는 방법은 도구마다 다릅니다. 테스트 도구는 이 값을 읽고 그 크기의 제한을 지키게 합니다.
작은 테스트로 붙인 테스트가 네트워크나 디스크에 손대면 도구가 그 테스트를 실패로 처리합니다.
이렇게 막아 두지 않으면 이름표가 금방 낡습니다. 누군가 작은 테스트에 데이터베이스 호출을 한 줄 넣으면 그 테스트는 이름만 작고 돌려 보면 느립니다. 도구가 막아 주면 그 한 줄을 넣는 순간 테스트가 깨져서 바로 압니다.
크기와 범위
테스트를 가르는 잣대는 크기 말고 하나 더 있습니다. 테스트 범위입니다. 범위는 테스트가 어느 코드를 확인하느냐를 봅니다. 함수 하나를 확인하면 범위가 좁습니다. 시스템 전체를 확인하면 넓습니다.
| 잣대 | 묻는 것 | 칸 |
|---|---|---|
| 크기 | 돌면서 무엇을 쓰나 · 어디서 도나 | 작은 · 중간 · 큰 |
| 범위 | 무엇을 확인하나 | 좁은 · 중간 · 넓은 |
두 잣대는 대개 함께 움직입니다. 범위가 좁은 테스트는 작은 테스트인 일이 많습니다. 그래도 늘 같이 가지는 않습니다.
클래스 하나만 확인하더라도 같은 머신에 진짜 데이터베이스를 띄워 붙이면 범위는 좁고 크기는 중간입니다. 반대로 주문 · 재고 · 결제를 한 프로세스 안에서 엮고 바깥 부품을 전부 가짜로 바꾸면 범위는 넓고 크기는 작습니다. 큰 테스트와 넓은 범위 테스트는 이름이 닮았지만 서로 다른 잣대의 말입니다.
크기를 고르는 때
세 칸은 구글이 자기 테스트를 나누려고 세운 틀입니다. 이 틀은 범위와 상관없이 되도록 작은 테스트를 쓰라고 권합니다. 같은 것을 확인할 수 있다면 더 작은 칸을 고르라는 뜻입니다.
작을수록 빨리 끝나서 코드를 고칠 때마다 돌릴 수 있습니다. 덜 흔들리니 깨졌을 때 믿을 수 있습니다.
그래도 작은 테스트만으로는 모자랍니다. 가짜 저장소는 만든 사람이 짐작한 대로만 움직입니다. 데이터베이스 주소가 틀렸거나 서버끼리 접근 권한이 막힌 잘못은 진짜를 이어 봐야 드러납니다. 그 몫은 중간 테스트와 큰 테스트가 맡습니다.
그래서 작은 테스트를 가장 많이 두고 큰 테스트를 가장 적게 두는 배분이 흔합니다. 개수를 이렇게 쌓으면 아래가 넓은 삼각형이 됩니다. 이 모양을 테스트 피라미드라고 부릅니다.
관련 항목
테스트 크기가 나누는 세 칸
테스트 크기와 나란히 테스트를 가르는 범위 잣대
테스트 범위 · 좁은 범위 테스트 · 중간 범위 테스트 · 넓은 범위 테스트 · 단위 테스트 · 통합 테스트 · 종단 간 테스트 · 테스트 수준
테스트 크기가 선을 긋는 실행 자원
프로세스 · 스레드 · 블로킹 · 디스크 입출력 · 네트워크 · 파일 시스템 · localhost · 데이터베이스
테스트를 작은 칸에 넣으려고 끼우는 가짜 부품
테스트 더블 · 페이크 · 목 객체 · 스텁 · 인메모리 데이터베이스
테스트 크기가 지키려는 성질과 그 반대 증상
결정성 · 허미틱 테스트 · 테스트 격리 · 불안정한 테스트
크기별 테스트 개수를 정하는 배분 모형
테스트 피라미드 · 테스팅 트로피 · 아이스크림 콘 안티패턴
크기를 붙인 테스트를 돌리고 지키게 하는 도구
다른 이름: test size · test sizes · 테스트 사이즈