중간 테스트
고친 사람 github-actions[bot]
중간 테스트는 머신 한 대 안에서만 도는 테스트입니다. 프로그램 하나 안에서 도는 작은 테스트와 여러 머신에 걸치는 큰 테스트 사이에 놓입니다. 머신 안에서는 진짜 데이터베이스나 서버를 붙여 놓고 확인해도 됩니다. 작은 테스트보다 느린 대신 프로그램끼리 이어 봐야 드러나는 잘못을 잡습니다.
쉽고 빠른 이해
무슨 일을 하나 — 테스트가 쓸 수 있는 것을 머신(컴퓨터) 한 대 안으로 묶어 둡니다. 주문 저장 코드를 확인할 때 가짜 저장소 대신 같은 머신에 띄운 진짜 데이터베이스에 붙여 돌리는 식입니다.
왜 이렇게 하나 — 가짜는 만든 사람이 짐작한 대로만 움직입니다. 쿼리가 진짜 데이터베이스에서도 통하는지는 붙여 봐야 압니다. 그래도 다른 머신까지 가면 너무 느리고 잘 흔들립니다. 그래서 한 대에서 멈춥니다.
어떻게 도나
- 확인할 서버와 데이터베이스를 테스트와 같은 머신에 띄웁니다
- 테스트가 그 머신 안에서 요청을 보냅니다
- 다른 머신으로 나가는 요청은 허락하지 않습니다
대가 — 프로그램을 띄우고 디스크를 쓰는 데 시간이 들어 작은 테스트보다 느립니다. 운영체제나 다른 프로그램 사정에 따라 결과가 가끔 흔들립니다.
언제 고르나 — 작은 테스트로 확인할 수 있으면 그쪽을 씁니다. 진짜 데이터베이스나 서버를 붙여야만 보이는 잘못이 있을 때 중간 테스트를 고릅니다.
상세
이 절은 테스트를 크기로 나누는 잣대부터 봅니다. 그다음 세 칸 중 가운데 칸이 무엇을 허락하고 무엇을 막는지 봅니다. 주문 서버와 데이터베이스를 한 머신에 띄운 테스트 한 벌을 가지고 중간 테스트가 얻는 것과 치르는 것을 보입니다.
밴드 연습을 떠올려 봅시다. 혼자 집에서 연습하면 언제 해도 금방 끝나고 조건이 늘 같습니다. 연습실에 모여 합주하면 서로 박자가 맞는지 들을 수 있습니다. 대신 모이고 장비를 꽂는 데 시간이 듭니다. 중간 테스트는 연습실 합주입니다.
테스트 크기라는 잣대
테스트 크기는 테스트가 어디서 도느냐로 테스트를 나눕니다. 코드 줄 수와는 상관이 없습니다. 이 잣대를 따로 두는 까닭은 테스트가 얼마나 빨리 끝나는지, 돌릴 때마다 같은 결과를 내는지가 도는 곳에 달려 있기 때문입니다.
잣대는 세 칸입니다. 이 세 칸은 구글이 자기 테스트를 나누려고 세운 틀입니다.
프로세스는 실행 중인 프로그램 하나입니다. 아래 표는 칸마다 테스트가 도는 곳을 모았습니다.
중간 테스트는 가운데 칸입니다. 작은 테스트에 걸린 제한을 대부분 풀고 하나만 남깁니다. 남는 제한이 「머신 한 대」입니다.
중간 테스트가 허락하는 것
작은 테스트가 막던 것이 이 칸에서 풀립니다. 이 소절은 풀리는 것을 하나씩 짚고 나서 표로 모읍니다.
첫째, 프로세스를 여럿 띄울 수 있습니다. 테스트 프로세스 옆에 데이터베이스 프로세스와 서버 프로세스를 따로 띄워 붙여도 됩니다.
둘째, 스레드를 여럿 써도 됩니다. 스레드는 한 프로세스 안에서 따로 도는 실행 흐름입니다. 서버 코드가 요청을 여러 흐름으로 나눠 받는다면 그대로 확인할 수 있습니다.
작은 테스트는 언어에 따라 스레드를 하나만 쓰게 막기도 합니다. 여러 흐름이 도는 순서는 매번 달라질 수 있어서 결과가 흔들리기 때문입니다.
셋째, 멈춰 기다리는 호출을 해도 됩니다. 이런 호출을 블로킹 호출이라고 부릅니다. 답이 올 때까지 프로그램이 그 줄에서 멈추기 때문입니다.
블로킹 호출의 대표가 입출력입니다. 입출력은 프로그램이 디스크나 네트워크처럼 바깥과 데이터를 주고받는 일입니다. 중간 테스트는 디스크에 파일을 쓰고 읽을 수 있습니다.
네트워크는 localhost 까지만 됩니다. localhost 는 자기 머신을 가리키는 이름입니다. localhost 로 보낸 요청은 네트워크 장비를 거치지 않고 같은 머신 안의 다른 프로세스에 닿습니다.
| 할 수 있는 것 | 작은 테스트 | 중간 테스트 |
|---|---|---|
| 프로세스 여럿 띄우기 | ✗ | ✓ |
| 스레드 여럿 쓰기 | 언어에 따라 막음 | ✓ |
| 디스크 읽기와 쓰기 | ✗ | ✓ |
| localhost 로 네트워크 호출 | ✗ | ✓ |
| 다른 머신으로 네트워크 호출 | ✗ | ✗ |
머신 밖을 막는 까닭
남은 제한 하나가 다른 머신으로 가는 네트워크입니다. 테스트를 느리게 하고 결과를 흔드는 원천 가운데 네트워크가 가장 큽니다. 그래서 다른 제한을 다 풀어도 이것 하나는 남겨 둡니다.
머신 한 대 안이면 테스트가 필요한 것을 전부 직접 띄우고 직접 끕니다. 다른 머신이 바쁜지, 그 사이 네트워크가 막혔는지 같은 사정을 만나지 않습니다. 결과는 테스트가 직접 띄우고 끄는 것에만 달려 있게 됩니다.
한 머신에 담긴 한 벌
이 소절은 중간 테스트 한 벌이 무엇을 머신 안에 두는지 그림으로 봅니다. 테스트가 주문 서버를 부르고, 주문 서버는 데이터베이스에 주문을 적습니다. 셋이 각각 다른 프로세스지만 모두 한 머신 위에 있습니다.
flowchart TD
subgraph M["머신 한 대"]
T["테스트"] -->|"localhost 요청"| O["주문 서버"]
O -->|"localhost 요청"| D["데이터베이스"]
end
O -.->|"막힘"| X["다른 머신의 서버"]
실선 둘은 머신 안에서 오가는 요청이라 허락됩니다. 점선은 머신 경계를 넘는 요청이라 막힙니다. 테스트 코드뿐 아니라 테스트가 띄운 서버도 머신 밖으로는 요청을 못 보냅니다. 이 점선이 허락되면 큰 테스트가 됩니다.
테스트 코드에서는 크기가 연결 주소에 드러납니다. 아래 코드는 주문 저장소를 같은 머신에 띄운 진짜 데이터베이스에 붙입니다. 5432 는 그 데이터베이스가 요청을 기다리는 포트 번호입니다. 포트는 한 머신 안에서 어느 프로그램에게 가는 요청인지 가르는 번호입니다.
var repo = new OrderRepository("localhost:5432");
repo.save(new Order(1, 3000));
repo.find(1).price(); // 3000
저장한 주문을 다시 꺼내면 3000 이 나옵니다. 이번에는 가짜 저장소가 아니라 디스크에 적힌 값을 데이터베이스가 읽어 준 것입니다. 쿼리 문법이 틀렸거나 테이블 모양이 코드와 어긋났다면 여기서 깨집니다.
느리고 흔들리는 까닭
이 칸에서 생기는 비용은 두 갈래입니다. 하나는 느려지는 것입니다. 다른 하나는 결과가 흔들리는 것입니다.
느려지는 까닭은 기다림이 생기기 때문입니다. 데이터베이스 프로세스를 띄우는 데 시간이 듭니다. 디스크에 쓰고 읽는 일은 메모리 안의 계산보다 훨씬 오래 걸립니다.
흔들림은 더 성가십니다. 프로세스와 스레드가 여럿이면 누가 먼저 도는지를 운영체제가 정합니다. 그 순서는 매번 달라질 수 있습니다.
같은 입력에 늘 같은 결과를 내는 성질을 결정성이라고 합니다. 실행 순서가 매번 달라지면 테스트는 이 성질을 잃기 쉽습니다.
순서에 따라 결과가 바뀌는 잘못을 경쟁 상태라고 부릅니다. 경쟁 상태가 있으면 코드를 하나도 안 바꿨는데 테스트가 통과했다 실패했다 합니다.
이렇게 코드가 그대로인데 결과가 바뀌는 테스트가 불안정한 테스트입니다. 중간 테스트는 작은 테스트보다 불안정한 테스트가 되기 쉽습니다.
중간 테스트를 쓰는 경우
같은 기능을 확인할 수 있다면 더 작은 테스트를 씁니다. 더 빨리 끝나고 결과도 덜 흔들리기 때문입니다. 중간 테스트는 작은 테스트로 확인할 수 없는 것이 있을 때 고릅니다. 그런 경우는 주로 둘입니다.
첫째는 진짜 데이터베이스와 함께 확인해야 할 때입니다. 메모리에 만든 가짜 저장소는 쿼리를 해석하지 않습니다. 쿼리가 진짜 데이터베이스에서도 통하는지는 진짜를 띄워 붙여 봐야 압니다.
둘째는 웹 화면과 서버 코드를 함께 확인해야 할 때입니다. 테스트 코드가 같은 머신에 진짜 브라우저를 띄우고 사람 대신 조작합니다. 버튼을 누르면 서버까지 요청이 가서 화면이 바뀌는지를 한 번에 봅니다.
개수로 보면 중간 테스트는 작은 테스트보다 적고 큰 테스트보다 많이 둡니다. 개수를 이렇게 쌓으면 아래가 넓은 삼각형이 됩니다. 이 모양을 테스트 피라미드라고 부릅니다.
중간 범위 테스트와 다른 축
중간 테스트와 중간 범위 테스트는 이름이 닮았지만 서로 다른 잣대의 말입니다. 테스트 범위는 테스트가 어느 코드를 확인하느냐를 봅니다. 크기는 테스트가 어디서 도느냐를 봅니다.
두 잣대는 자주 함께 움직입니다. 클래스 몇 개가 데이터베이스와 잘 맞물리는지 확인하는 테스트는 범위가 중간입니다. 그 데이터베이스를 같은 머신에 띄우면 크기도 중간입니다. 그래도 늘 같이 가지는 않습니다.
클래스 하나만 확인하는데 같은 머신의 진짜 데이터베이스에 붙이면 범위는 좁고 크기는 중간입니다. 반대로 시스템 전체를 머신 한 대에 몰아 띄워 처음부터 끝까지 확인하면 범위는 넓고 크기는 중간입니다.
관련 항목
중간 테스트와 같은 크기 잣대로 나뉘는 테스트
테스트 크기 · 작은 테스트 · 큰 테스트 · 허미틱 테스트
중간 테스트와 다른 축인 범위로 나뉘는 테스트
테스트 범위 · 좁은 범위 테스트 · 중간 범위 테스트 · 넓은 범위 테스트 · 단위 테스트 · 통합 테스트 · 종단 간 테스트
중간 테스트가 머신 안에 띄워 붙이는 진짜 부품
데이터베이스 · 프로세스 · localhost · 포트 · 테스트 컨테이너 · 헤드리스 브라우저 · WebDriver
중간 테스트가 잃기 쉬운 성질과 그 증상
결정성 · 경쟁 상태 · 불안정한 테스트 · 테스트 격리 · 스레드 · 블로킹 · 입출력
중간 테스트 대신 쓸 수 있는 수단
테스트 더블 · 페이크 객체 · 인메모리 데이터베이스 · 계약 테스트
중간 테스트의 개수를 정하는 배분 모양
다른 이름: medium test · medium tests · 중형 테스트