사전 테스트 범위
트레이드오프축

테스트 범위

gabury1고친 사람 github-actions[bot]

테스트 범위는 테스트 하나가 코드를 얼마나 넓게 확인하는지를 가릅니다. 좁은 테스트는 빠르고 깨진 곳을 바로 짚습니다. 넓은 테스트는 부분을 이은 곳의 잘못까지 잡는 대신 느립니다. 테스트 계획서에서 무엇을 테스트하고 무엇을 뺄지 정한 목록도 같은 이름으로 부릅니다.

쉽고 빠른 이해

무슨 일을 하나 — 테스트 하나가 맞는지 확인하는 코드가 얼마나 넓은지로 테스트를 나눕니다. 메서드 하나만 보는 테스트는 좁습니다. 화면에서 주문 버튼을 눌러 결제까지 이어지는지 보는 테스트는 넓습니다.

왜 이렇게 나누나 — 넓이에 따라 얻는 것과 잃는 것이 맞바뀝니다. 「단위 테스트」「통합 테스트」 같은 이름은 팀마다 뜻이 다릅니다. 넓이라는 잣대로 재야 같은 테스트를 같은 말로 부를 수 있습니다.

어떻게 도나

  1. 테스트마다 무엇을 확인하는지 정합니다
  2. 부분 하나면 좁음, 부분 몇 개 사이면 중간, 시스템 전체면 넓음으로 칸을 매깁니다
  3. 칸마다 테스트를 몇 개 둘지 정합니다

언제 어느 범위를 고르나 — 확인하려는 것이 메서드 안에 있으면 좁게 잡습니다. 두 부분 사이의 약속이면 중간으로, 사용자가 겪는 흐름 전체면 넓게 잡습니다.

대가 — 좁히면 부분끼리 이어지는 곳의 잘못을 못 봅니다. 넓히면 느려집니다. 깨졌을 때 어디가 틀렸는지 찾기도 어렵습니다.

상세

이 절은 테스트 범위가 무엇을 재는지 정합니다. 그다음 좁은 끝과 넓은 끝에서 얻고 잃는 것을 짝지어 봅니다. 예로 드는 것은 작은 쇼핑몰입니다. 사용자가 화면에서 주문 버튼을 누르면 주문 서비스가 합계를 냅니다. 이어서 주문을 데이터베이스에 저장한 뒤 결제 서비스를 부릅니다.

자동차 정비소를 떠올려 봅시다. 부품 하나를 떼어 시험대에 올리면 그 부품이 제대로 도는지 금방 압니다. 차를 몰고 도로를 한 바퀴 돌면 부품들이 서로 맞물려 도는지 압니다. 그 대신 이상한 소리가 나도 어느 부품 탓인지 바로 모릅니다.

확인하는 코드의 양

테스트 범위는 테스트 하나가 맞는지 확인하는 코드의 양입니다. 확인하는 코드가 적을수록 범위가 좁습니다. 주문 서비스의 합계 계산 하나만 보는 테스트는 좁습니다. 주문 버튼부터 결제까지 보는 테스트는 넓습니다.

이 잣대가 필요한 까닭은 이름이 흔들리기 때문입니다. 어느 팀은 데이터베이스를 붙인 테스트를 통합 테스트라고 부릅니다. 다른 팀은 서비스 둘을 띄워 붙인 것만 그렇게 부릅니다. 넓이로 재면 이름이 달라도 같은 테스트를 같은 칸에 놓을 수 있습니다.

범위는 보통 세 칸으로 나눕니다. 아래 표는 칸마다 무엇을 확인하고 흔히 어떤 이름으로 부르는지 모았습니다.

범위 확인하는 것 흔히 부르는 이름
좁은 범위 테스트 클래스 하나나 메서드 하나의 논리 단위 테스트
중간 범위 테스트 부분 몇 개가 서로 주고받는 것 통합 테스트
넓은 범위 테스트 시스템의 여러 부분이 함께 내는 동작 종단 간 테스트 · 시스템 테스트

표의 세 칸을 쇼핑몰 하나에 겹쳐 놓으면 아래 그림이 됩니다. 좁은 칸은 합계 계산 하나입니다. 중간 칸은 주문 서비스와 데이터베이스 사이입니다. 넓은 칸은 화면부터 결제 서비스까지입니다.

flowchart TD
    subgraph 넓음["넓은 범위 · 화면부터 결제까지"]
        U["화면"]
        subgraph 중간["중간 범위 · 주문 서비스와 데이터베이스"]
            subgraph 좁음["좁은 범위 · 합계 계산 하나"]
                T["합계 계산"]
            end
            S["주문 서비스"]
            DB["데이터베이스"]
        end
        P["결제 서비스"]
    end
    U --> S
    S --> T
    S --> DB
    S --> P

상자가 안쪽으로 들어갈수록 확인하는 코드가 줄어듭니다. 바깥 상자는 안쪽 상자를 전부 품습니다. 넓은 범위 테스트가 깨졌을 때 원인을 찾기 어려운 것도 이 때문입니다. 볼 곳이 그림 전체입니다.

실행되는 코드와 확인하는 코드

범위를 잴 때 세는 것은 확인하는 코드입니다. 테스트가 도는 동안 실행되는 코드가 아닙니다. 이 소절은 그 차이를 합계 계산 테스트 하나로 봅니다.

합계를 내는 메서드는 주문 서비스의 total 입니다. 상품 값을 더한 뒤 할인 계산기를 불러 할인을 붙입니다. 아래 테스트는 할인 대상이 아닌 상품 두 개의 값을 넣고 합계가 맞는지 봅니다. 자바의 테스트 도구인 JUnit 으로 썼습니다.

Java
@Test
void 상품_값을_더한다() {
    var s = new OrderService();
    int t = s.total(3000, 4000); // 7000
    assertEquals(7000, t);
}

total 이 7천 원을 돌려줍니다. 마지막 줄은 그 값이 7천인지 확인합니다. 결과를 이렇게 확인하는 줄을 단언이라고 부릅니다.

이 테스트가 도는 동안 할인 계산기의 코드도 실행됩니다. 다만 할인 대상이 없으니 할인은 0원이고, 단언한 7천은 두 값을 더한 수 그대로입니다. 할인이 붙는 경우를 하나도 넣지 않았으니 이 테스트가 확인하는 것은 더하기 하나입니다.

그래서 이 테스트의 범위는 좁습니다. 할인 계산기가 맞는지는 할인 계산기를 겨냥한 다른 테스트가 확인합니다.

좁은 끝에서 얻는 것과 잃는 것

좁은 범위 테스트는 부분 하나만 부르므로 금방 끝납니다. 몇 천 개를 두어도 코드를 고칠 때마다 다 돌려 볼 수 있습니다. 깨지면 테스트 이름만 보고도 고칠 메서드를 압니다.

느리거나 결과가 매번 바뀌는 것은 좁은 범위에서 빼기 쉽습니다. 데이터베이스나 다른 서비스 대신 테스트 더블을 끼우면 됩니다. 테스트 더블은 테스트에서 실제 구현 대신 쓰는 가짜입니다. 덕분에 돌릴 때마다 같은 결과가 나옵니다.

잃는 것은 부분과 부분 사이입니다. 주문 서비스는 금액을 원 단위 정수로 보낸다고 해 봅시다. 그런데 결제 서비스는 소수점 붙은 문자열이 온다고 짐작한 채 짜였습니다. 두 서비스의 좁은 범위 테스트가 전부 통과해도 둘을 이으면 결제가 금액을 못 읽습니다.

테스트 더블이 이 틈을 키웁니다. 가짜는 테스트를 쓴 사람이 짐작한 대로만 움직이기 때문입니다. 짐작이 실제 동작과 다르면 테스트는 통과하는데 합친 프로그램은 깨집니다.

넓은 끝에서 얻는 것과 잃는 것

넓은 범위 테스트는 사용자가 겪는 흐름을 그대로 따라갑니다. 주문 버튼부터 결제까지 실제 부분을 그대로 이어서 돌립니다. 그래서 좁은 범위가 못 보는 부분과 부분 사이의 잘못을 잡습니다.

넓은 범위 테스트는 코드 안쪽의 생김새에 덜 묶입니다. 겉에서 보이는 동작만 확인하기 때문입니다. 클래스를 쪼개거나 합쳐도 사용자가 겪는 흐름이 같으면 테스트가 안 깨집니다.

잃는 것은 속도와 안정입니다. 데이터베이스와 다른 서비스를 실제로 띄우느라 한 번 도는 데 오래 걸립니다. 네트워크가 잠깐 늦거나 앞 테스트가 남긴 데이터가 끼어들면 코드가 멀쩡해도 깨집니다. 이렇게 같은 코드에서 붙었다 떨어졌다 하는 테스트를 불안정한 테스트라고 부릅니다.

깨졌을 때 원인을 찾기도 어렵습니다. 그림의 바깥 상자 전체가 용의자입니다. 화면인지, 주문 서비스인지, 결제 서비스인지 로그를 따라가며 좁혀야 합니다.

양 끝의 짝

두 끝이 무엇을 맞바꾸는지 한 표에 모으면 아래와 같습니다. 한 줄에서 한쪽이 얻는 것은 다른 쪽이 잃는 것입니다.

좁은 끝 넓은 끝
한 번 도는 시간 짧다 길다
깨졌을 때 볼 곳 메서드 하나 시스템 전체
결과의 흔들림 적다 크다
부분 사이의 잘못 못 잡는다 잡는다
안쪽 구조를 바꿀 때 함께 고칠 테스트가 많다 동작이 같으면 안 깨진다

어느 끝도 늘 낫지 않습니다. 그래서 범위는 테스트를 쓸 때마다 고르는 값입니다.

범위를 고르는 조건

범위는 테스트가 확인하려는 것이 어디에 사는지를 보고 고릅니다. 확인하려는 것이 사는 곳보다 넓게 잡으면 느려지기만 합니다. 좁게 잡으면 확인하려는 것이 테스트 밖으로 빠집니다.

아래 표에는 스키마라는 낱말이 나옵니다. 스키마는 데이터베이스의 테이블과 열이 어떻게 생겼는지 정한 것입니다. 쿼리가 없는 열을 부르면 이 생김새와 어긋나서 실패합니다.

확인하려는 것 고르는 범위 까닭
메서드 하나 안의 계산과 분기 좁은 범위 그 메서드만 불러도 전부 드러난다
쿼리가 실제 스키마에서 도는지 중간 범위 데이터베이스를 가짜로 바꾸면 확인할 것이 사라진다
다른 서비스가 보내는 JSON(JavaScript Object Notation) 모양을 읽는지 중간 범위 두 부분 사이의 약속이라 한쪽만으로는 안 보인다
가입에서 주문, 결제까지 이어지는지 넓은 범위 흐름 전체가 확인할 대상이다

한 프로젝트의 테스트 전체는 한쪽 끝만 고르지 않습니다. 세 칸을 섞어 둡니다. 칸마다 몇 개를 둘지는 따로 정합니다. 그 개수의 모양을 다루는 것이 테스트 피라미드와 테스팅 트로피입니다.

테스트 크기와 헷갈리는 축

테스트를 가르는 잣대가 범위 말고도 있습니다. 테스트 크기는 테스트가 어디서 도느냐로 가릅니다. 프로세스 하나 안에서 도는지, 머신 한 대 안에서 도는지, 여러 머신에 걸쳐 도는지를 봅니다. 차례로 작은 테스트 · 중간 테스트 · 큰 테스트라고 부릅니다.

두 축은 따로 움직입니다. 주문을 저장하는 클래스를 같은 머신에 띄운 실제 데이터베이스에 붙여 시험한다고 해 봅시다. 단언이 클래스의 논리만 본다면 데이터베이스는 실행될 뿐 확인 대상이 아닙니다. 「실행되는 코드와 확인하는 코드」의 할인 계산기와 같은 경우라 범위는 좁습니다.

크기는 다르게 나옵니다. 데이터베이스가 따로 떠서 프로세스가 둘이 되니 크기는 중간입니다. 확인하려는 것이 쿼리가 실제 스키마에서 도는지라면 이야기가 달라집니다. 데이터베이스까지 확인 대상이 되어 범위도 중간이 됩니다.

테스트 커버리지와 헷갈리는 축

테스트 커버리지는 테스트 전체가 돌면서 실행한 코드 줄이 얼마나 되는지를 비율로 잽니다. 테스트 묶음 전체를 두고 매기는 값입니다.

범위는 테스트 하나를 두고 매깁니다. 그리고 실행한 코드가 아니라 확인하는 코드를 셉니다. 앞의 합계 테스트는 할인 계산기를 실행했으니 커버리지에는 잡힙니다. 그래도 그 테스트의 범위에 할인 계산기는 들지 않습니다.

테스트 계획서의 범위

같은 이름이 전혀 다른 뜻으로도 쓰입니다. 품질 보증 팀이 쓰는 테스트 계획서에서 테스트 범위는 이번에 테스트할 기능과 뺄 기능을 적은 목록입니다.

이 뜻의 범위는 테스트 하나가 아니라 테스트 일 전체의 경계를 정합니다. 예를 들어 「이번 출시는 결제 화면만 테스트하고 관리자 화면은 뺀다」가 그런 범위입니다. 앞의 소절들이 다룬 넓이와는 다른 이야기입니다.

관련 항목

테스트 범위로 나뉘는 테스트

좁은 범위 테스트 · 중간 범위 테스트 · 넓은 범위 테스트 · 단위 테스트 · 통합 테스트 · 종단 간 테스트 · 시스템 테스트 · 기능 테스트 · 인수 테스트

테스트 범위와 헷갈리는 다른 잣대

테스트 크기 · 작은 테스트 · 중간 테스트 · 큰 테스트 · 테스트 커버리지 · 테스트 수준

범위별 테스트 개수를 정하는 모양

테스트 피라미드 · 테스팅 트로피 · 아이스크림 콘 안티패턴 · 모래시계 안티패턴

범위를 좁힐 때 진짜 대신 끼우는 가짜

테스트 더블 · 스텁 · 목 객체 · 페이크 · 모킹 프레임워크 · 의존성 주입

범위를 잘못 잡으면 나는 문제

불안정한 테스트 · 깨지기 쉬운 테스트 · 테스트 순서 의존성

테스트 일 전체의 범위를 정하는 문서

테스트 계획 · 테스트 케이스 · 테스트 스위트 · 인수 조건

테스트 범위가 속하는 상위 분류

QA와 테스트 · 소프트웨어 테스트 · 자동 테스트 · 테스트 자동화

다른 이름: test scope · scope of a test · 테스트 스코프