개념
왜 기법이 필요한가
"나이 입력 필드"를 테스트한다고 하자. 입력 가능한 값은 사실상 무한하다. 아무 값이나 20개 넣어보는 것과, 기법으로 6개를 고르는 것 중 후자가 결함을 더 많이 찾는다. 테스트 설계 기법은 요령이 아니라 커버리지를 근거로 설명할 수 있게 만드는 도구다.
커버리지(coverage) 는 "확인해야 할 것 중 얼마나 덮었는가" 를 가리킨다. "20개를 넣어 봤다" 는 양을 말할 뿐이지만, "값이 갈라지는 구간 세 개를 전부 덮었다" 는 무엇을 덮었고 무엇을 안 덮었는지를 말한다. 뒤의 문장만 근거가 된다. 코드 수준에서 이걸 숫자로 재는 방법은 레슨 7에서 다룬다.
비유를 하나 들면 이해가 빠르다. 긴 울타리에 구멍이 있는지 확인해야 한다고 하자. 아무 데나 스무 곳을 두드려보는 것보다, 판자와 판자가 만나는 이음새를 여섯 곳 확인하는 편이 훨씬 빨리 구멍을 찾는다. 판자 한가운데는 어차피 같은 나무이고, 문제가 생기는 곳은 늘 이음새이기 때문이다. 동등분할은 "판자가 몇 장인지" 세는 일이고, 경계값 분석은 "이음새를 두드리는" 일이다.
이 기법이 주는 이득은 세 가지다.
| 이득 | 구체적으로 |
|---|---|
| 케이스 수가 줄어든다 | 무한한 입력 → 판자 수만큼의 대표값 |
| 놓치는 곳이 줄어든다 | 이음새(경계)를 빠뜨리지 않는 절차가 생긴다 |
| 설명할 수 있게 된다 | "왜 이 값을 넣었나요?"에 근거로 답할 수 있다 |
세 번째가 특히 중요하다. 실무에서 테스트 케이스는 혼자 만들고 끝나는 문서가 아니라 리뷰를 통과해야 하는 산출물이다. 근거 없이 만든 20건보다 근거 있는 6건이 리뷰에서 살아남는다.
동등분할 (Equivalence Partitioning)
같은 방식으로 처리될 입력들을 하나의 그룹으로 묶고, 그룹당 대표값 하나만 테스트한다.
요구사항: 나이는 19세 이상 65세 이하만 가입할 수 있다.
| 파티션 | 종류 | 대표값 |
|---|---|---|
| 19 미만 | 비유효 | 15 |
| 19 ~ 65 | 유효 | 30 |
| 65 초과 | 비유효 | 70 |
| 숫자가 아님 | 비유효 | "abc" |
| 비어 있음 | 비유효 | "" |
핵심 규칙 두 가지가 있다.
- 유효 파티션은 한 번에 여러 개를 조합해도 된다. (통과하는 케이스는 묶어서 확인)
- 비유효 파티션은 반드시 한 번에 하나씩 테스트한다. 두 개를 동시에 위반하면 어느 쪽 검증 로직이 동작한 건지 알 수 없다. 앞선 검증에 가려져 뒤쪽 검증의 버그를 놓치는 현상을 결함 마스킹(fault masking) 이라고 한다.
파티션을 다 나눴다면 두 가지를 자문해 검산한다.
- 완전성: 어떤 입력을 가져와도 반드시 어느 한 파티션에 속하는가? 속하지 않는 입력이
떠오른다면 파티션이 빠진 것이다. (예: 나이에 소수점
19.5를 넣으면 어디에 속하는가?) - 배타성: 두 파티션에 동시에 속하는 입력이 있는가? 있다면 파티션 정의가 겹친 것이고, 대개 그 겹치는 지점이 명세의 빈틈이다.
동등분할을 잘못 나누는 두 가지 방식
기법을 배운 직후 가장 많이 하는 실수는 "파티션을 몇 개로 나눌 것인가"를 감으로 정하는 것이다. 잘못되는 방향은 정확히 두 가지다.
① 너무 잘게 쪼갠다 — 케이스가 폭발한다.
나이 19~65의 유효 구간을 "10대 후반 / 20대 / 30대 / 40대 / 50대 / 60대 초반"으로 나누는 식이다. 언뜻 꼼꼼해 보이지만, 코드는 이 여섯 구간을 완전히 같은 방식으로 처리한다. 케이스는 6배가 됐는데 새로 잡히는 결함은 0건이다. 이건 꼼꼼함이 아니라 낭비이고, 실행 시간을 잡아먹어 정작 봐야 할 곳에 쓸 시간을 빼앗는다.
② 너무 성기게 묶는다 — 서로 다른 처리 경로가 한 덩어리가 된다.
반대 방향이 더 위험하다. "로그인한 회원"을 하나의 유효 파티션으로 묶었는데, 실제 코드에서는 일반 회원·VIP 회원·휴면 회원이 각각 다른 분기를 탄다면 어떻게 될까. 대표값으로 일반 회원 하나만 넣고 통과 판정을 내리는 순간, VIP와 휴면 경로는 한 번도 실행되지 않은 채 릴리즈된다. 케이스 수는 적어 보이지만 커버리지에 구멍이 뚫린 것이다.
두 실수를 가르는 판별 기준은 하나다.
"이 두 입력이 서로 다른 코드 경로(또는 서로 다른 결과)를 만드는가?" 그렇다면 나눈다. 아니라면 나누지 않는다.
| 잘못 나눈 예 | 어느 쪽 실수 | 왜 문제인가 | 어떻게 고치나 |
|---|---|---|---|
| 유효 나이를 10년 단위로 6등분 | 너무 잘게 | 처리 경로가 동일 — 케이스만 6배 | 유효 구간은 대표값 1개로 통합 |
| "잘못된 입력"을 한 파티션으로 통합 | 너무 성기게 | 빈 값·타입 오류·범위 초과는 검증 로직이 다름 | 원인별로 분리 |
| 결제수단을 "카드 / 그 외"로만 구분 | 너무 성기게 | 간편결제·계좌이체·포인트는 승인 흐름이 다름 | 흐름이 다른 수단마다 파티션 |
| 파일 크기를 1MB 단위로 나열 | 너무 잘게 | 제한값 하나만 분기 조건 | 제한 미만 | 제한 초과 두 개면 충분 |
| "유효한 이미지 파일" 하나로 통합 | 너무 성기게 | PNG·JPEG·HEIC·GIF는 디코딩 경로가 다름 | 처리기가 다른 포맷별로 분리 |
성기게 나눈 실수를 찾는 실전 요령이 하나 있다. 명세나 화면에서 "~인 경우에는" 이라는
표현이 나오는 곳, 코드에서 if가 갈라지는 곳을 찾는다. 그 지점이 곧 파티션 경계다.
이 감각은 스테이지 3에서 코드를 읽을 때, 그리고 이 모듈 레슨 7의 구조 기반 커버리지에서
다시 만나게 된다.
경계값 분석 (Boundary Value Analysis)
결함은 파티션 한가운데가 아니라 경계에서 나온다. 이유는 코드를 보면 자명하다.
// 요구사항: 19세 이상 65세 이하 가입 가능
function canSignUp(age) {
return age > 19 && age < 65; // 버그: >= 와 <= 여야 한다
}이 코드는 나이 30을 넣으면 정상 통과한다. 19와 65를 넣어야만 드러난다.
> 와 >=, < 와 <=, i < n 과 i <= n 을 헷갈리는 것은 경력과 무관하게 계속 발생한다. 그래서 경계는 항상 본다.
경계값은 두 방식이 있다.
2-value (경계 ± 1) — 실무에서 가장 흔하다.
19세 이상 65세 이하 → 18, 19 / 65, 663-value (경계 앞·경계·경계 뒤) — 리스크가 큰 영역에서 쓴다.
19세 이상 65세 이하 → 18, 19, 20 / 64, 65, 66둘 중 무엇을 쓸지는 감이 아니라 리스크로 정한다. 결제·정산·인증·개인정보처럼 틀렸을 때 비용이 큰 영역은 3-value, 그 외 일반 입력 검증은 2-value가 실무의 기본값이다.
경계값을 고르는 절차는 세 단계로 기계화할 수 있다.
- 파티션을 나눈다. (앞 절)
- 인접한 두 파티션이 맞닿는 지점을 찾는다. 그 지점이 경계다. 파티션이 5개면 맞닿는 지점은 4개이고, 경계 후보는 그 4개에서만 나온다.
- 각 경계마다 "바로 아래 / 경계 / 바로 위"를 적는다. "바로"가 무엇인지는 데이터 타입이 결정한다 — 정수면 1, 금액(원)이면 1원, 소수점 둘째 자리까지 쓰는 값이면 0.01이다.
3번의 마지막 문장이 실수가 잦은 곳이다. 경계 ±1의 "1"은 항상 1이 아니다. 소수점을 허용하는 필드에서 30,000원의 경계를 29,999원으로 잡으면, 29,999.99원이라는 진짜 경계를 비껴간다. 날짜라면 하루가 아니라 1초일 수도 있다. "이 필드가 표현할 수 있는 가장 가까운 다음 값은 무엇인가"를 먼저 묻는 습관이 필요하다.
코드에서 경계 버그가 나오는 실제 패턴들
> 와 >= 만 경계 버그인 것은 아니다. 실무에서 반복적으로 나타나는 패턴이 몇 가지 더 있고,
패턴을 알아두면 코드를 보지 않고도 어디를 찌를지 짐작할 수 있다.
패턴 1 — 반복문 off-by-one
가장 고전적인 경계 버그다. "하나 더 돌거나 하나 덜 도는" 실수를 off-by-one 에러 라고 부른다.
# 요구사항: 최근 7일치 데이터를 보여준다
for i in range(7): # 0,1,2,3,4,5,6 — 7일치. 정상
show(day[i])
for i in range(1, 7): # 1,2,3,4,5,6 — 6일치. 하루가 빠졌다
show(day[i])
for i in range(8): # 0~7 — 8일치. 하루가 더 나오거나 에러
show(day[i])Python의 range(n)은 n을 포함하지 않고 n-1까지 돈다. 이 규칙 하나를 헷갈리면
"최근 7일"이 6일이 되거나 8일이 된다. JavaScript도 같은 함정이 있다.
for (let i = 0; i <= items.length; i++) { // 버그: <= 여야 할 이유가 없다
total += items[i].price; // 마지막 회차에서 items[length]는 undefined
}QA가 이 패턴을 잡아내는 방법은 코드를 읽는 것이 아니라 개수의 경계를 찌르는 것이다. 목록이 0건일 때, 1건일 때, 정확히 표시 한도만큼일 때, 한도보다 1건 많을 때. "최근 7일"이라면 데이터가 정확히 7일치일 때와 6일치·8일치일 때를 각각 확인한다.
패턴 2 — 배열 인덱스, 0부터냐 1부터냐
프로그래밍 언어의 배열은 0번부터 세는데, 사람이 쓰는 화면은 1번부터 센다. 이 두 세계가 만나는 지점에서 경계 버그가 난다.
// 3개짜리 목록: 유효한 인덱스는 0, 1, 2 — [3]은 존재하지 않는다
const items = ["키보드", "마우스", "모니터"];
items[3]; // undefined
items[items.length - 1]; // 마지막 항목 — 이렇게 써야 안전하다증상은 대개 이렇게 나타난다.
| 증상 | 의심할 것 |
|---|---|
| 목록의 첫 항목만 빠지거나 중복된다 | 1부터 세는 값을 0부터 세는 인덱스로 그대로 씀 |
| 목록의 마지막 항목만 안 보인다 | i < length 대신 i < length - 1 |
| 마지막에서 에러가 난다 | i <= length — 없는 인덱스에 접근 |
| 항목이 1개일 때만 깨진다 | "첫 항목"과 "마지막 항목"이 같은 상황을 안 다룸 |
마지막 줄이 QA에게 특히 유용하다. 항목이 정확히 1개인 상황은 코드가 "여러 개"를 가정하고 짜였을 때 가장 먼저 깨지는 경계다. 목록 화면을 만나면 반사적으로 0건·1건을 본다.
패턴 3 — 페이지네이션 계산식
목록을 페이지로 나누는 계산은 경계 버그의 온상이다. 공식 자체는 짧은데, 그 안에 "0부터/1부터", "나눗셈의 나머지", "총 개수 0" 이 전부 들어 있기 때문이다.
const totalPages = Math.ceil(totalCount / pageSize); // 올림. floor면 마지막 페이지가 사라진다
const offset = (page - 1) * pageSize; // page는 1부터 — 0이 들어오면 음수가 된다여기서 나오는 대표적인 결함과, 그것을 잡아내는 테스트 값을 묶으면 이렇다.
| 흔한 실수 | 증상 | 찌를 값 |
|---|---|---|
Math.floor로 총 페이지 계산 | 마지막 몇 건이 어느 페이지에서도 안 보인다 | 총 21건 / 페이지당 10건 |
| 나누어떨어질 때 페이지를 하나 더 만듦 | 마지막에 빈 페이지가 생긴다 | 총 20건 / 페이지당 10건 |
page 하한 검증 없음 | page=0이면 offset이 음수 → 에러 또는 전체 노출 | page=0, page=-1 |
page 상한 검증 없음 | 마지막 페이지 다음을 요청하면 빈 화면 | 에러 | page=(총페이지+1) |
| 총 건수 0 처리 없음 | "0페이지 중 1페이지" 같은 문구 | 나누기 오류 | 총 0건 |
| 페이지 이동 중 데이터 변경 | 항목이 건너뛰어지거나 중복된다 | 1페이지 조회 후 항목 삭제 → 2페이지 |
마지막 줄은 계산식 문제가 아니라 시간의 경계 문제다. 정적인 값만 경계로 보는 습관에서 벗어나면 이런 결함이 보이기 시작한다.
패턴 4 — 반올림과 잘림
금액·할인율·평점처럼 소수점이 개입하는 계산에서는 경계가 소수점 아래에 숨는다.
// 10% 할인, 19,999원 상품
19999 * 0.9 // = 17999.1 → 화면에는 17,999원? 18,000원? 17,999.1원?절사(버림)냐 반올림이냐에 따라 1원이 달라지고, 그 1원이 무료배송 기준선을 넘느냐 마느냐를 가르면 결함이 된다. 소수점이 보이면 "이 값은 어디서 어떻게 정수가 되는가"를 묻는다.
여러 파라미터의 경계가 만날 때
지금까지는 파라미터가 하나였다. 실제 기능은 대개 두 개 이상의 입력을 함께 본다.
요구사항: 주문 금액 30,000원 이상 이고 총 무게 20kg 이하 이면 무료배송.
각 파라미터의 경계를 따로따로 테스트하면 이런 표가 나온다.
| TC | 금액 | 무게 | 무엇을 보는가 |
|---|---|---|---|
| 1 | 29,999 | 5kg | 금액 하한 −1 |
| 2 | 30,000 | 5kg | 금액 하한 |
| 3 | 50,000 | 20kg | 무게 상한 |
| 4 | 50,000 | 20.1kg | 무게 상한 +1 |
문제는 이 넷 중 두 경계가 동시에 걸린 케이스가 하나도 없다는 점이다. 금액을 볼 때는 무게를 안전한 값(5kg)으로, 무게를 볼 때는 금액을 안전한 값(50,000원)으로 잡았기 때문이다. 정확히 30,000원이면서 정확히 20kg인 주문에서만 터지는 결함이 있다면 이 표는 통과한다.
경계가 동시에 걸리는 지점을 코너 케이스(corner case) 라고 부른다. 사각형의 변이 아니라 꼭짓점이라는 뜻이다. 상호작용 결함은 대개 이 꼭짓점에 있다.
// 무게를 kg 단위 정수로 반올림한 뒤 비교한다면?
if (total >= 30000 && Math.round(weight) <= 20) { ... }
// 20.4kg는 20으로 반올림돼 통과한다 — 무게 경계를 단독으로 볼 땐 안 드러난다그렇다고 모든 조합을 다 만들 수는 없다. 파라미터가 3개이고 각각 경계값이 4개면 조합은 4×4×4 = 64건이고, 파라미터가 하나 늘 때마다 곱셈으로 불어난다. 여기서 실무의 선택지는 세 가지다.
| 접근 | 언제 쓰나 | 대가 |
|---|---|---|
| 각 파라미터 독립 경계만 | 파라미터끼리 상호작용이 없다고 판단될 때 | 코너 케이스를 놓친다 |
| 리스크 높은 쌍만 코너 조합 추가 | 대부분의 실무 상황 | 판단 근거가 필요하다 |
| 전수 조합 | 결제·정산처럼 틀리면 치명적일 때 | 케이스 수가 폭발한다 |
두 번째가 현실적인 답이고, 이걸 체계적으로 하는 방법이 이 모듈 레슨 5의 페어와이즈 (pairwise) 조합 테스트 다. "모든 조합"이 아니라 "모든 두 값의 쌍"만 덮으면 조합 결함의 대부분이 잡힌다는 아이디어다. 여기서는 경계값끼리도 조합해봐야 한다는 감각만 챙기고 넘어가면 된다. 조건이 표 형태로 정리되는 경우라면 레슨 3의 결정 테이블(조건들의 켜짐·꺼짐 조합을 표의 열로 늘어놓고 각 조합에서 무엇이 일어나야 하는지 적는 방법)이 먼저 어울린다.
지금 단계에서 쓸 수 있는 실용적인 규칙은 이 정도다.
파라미터가 여러 개일 때, 모든 파라미터가 동시에 유효 경계에 걸린 케이스 1건과 동시에 비유효 경계에 걸린 케이스 1건은 최소한 넣는다.
놓치기 쉬운 경계들
숫자 범위만 경계가 아니다. 실무 결함의 상당수가 여기서 나온다.
| 대상 | 경계 후보 |
|---|---|
| 문자열 길이 | 0자, 1자, 최대값, 최대값+1 |
| 목록·페이징 | 0건, 1건, 한 페이지 정확히 채움, +1건 |
| 날짜 | 월말, 윤년 2/29, 연말연시, 자정 직전·직후 |
| 시간대 | UTC 자정, 서머타임 전환, KST 09:00 경계 |
| 금액 | 0원, 최소 결제 금액, 무료 배송 기준선, 최대 한도 |
| 파일 | 0바이트, 최대 크기, 최대 크기 + 1바이트 |
| 권한·수량 | 재고 0개, 마지막 1개, 동시 구매 |
| 문자 종류 | 공백만 입력, 이모지, 한글 조합 중 상태, 개행 포함 |
특히 "정확히 기준선에 걸친 값" 이 위험하다. 무료 배송 기준이 30,000원일 때 정확히 30,000원을 주문하면 무료인가 아닌가? 요구사항에 안 적혀 있다면 그건 결함이 아니라 명세 결함이고, 개발이 시작되기 전에 물어보는 것이 QA의 일이다.
문자열 길이 경계에는 함정이 하나 더 있다. "10자"의 1자를 무엇으로 세는가가 정해져 있지 않은 경우가 많다. 한글 1자는 UTF-8에서 3바이트이고, 이모지 하나는 4바이트 이상이며, 어떤 이모지는 여러 글자가 합쳐진 형태다. 화면에서는 10자인데 서버에서는 30바이트 제한에 걸려 거부되는 상황이 여기서 나온다. 길이 제한을 보면 "글자 수인가 바이트 수인가" 를 묻는다.
두 기법은 항상 세트로 쓴다
동등분할로 어디를 볼지 정하고, 경계값으로 그 안 어디를 찌를지 정한다.
요구사항: 주문 금액 30,000원 이상이면 배송비 무료, 미만이면 3,000원.
| TC | 입력 | 기법 | 기대 결과 |
|---|---|---|---|
| 1 | 29,999 | 경계 −1 | 배송비 3,000원 |
| 2 | 30,000 | 경계 | 배송비 0원 |
| 3 | 30,001 | 경계 +1 | 배송비 0원 |
| 4 | 15,000 | 유효 파티션 대표 | 배송비 3,000원 |
| 5 | 0 | 경계 (하한) | 주문 불가 |
| 6 | −1000 | 비유효 | 입력 거부 |
6건이면 이 요구사항은 충분히 덮인다. 무작정 20건 만드는 것보다 낫고, 각 케이스가 왜 존재하는지 설명할 수 있다. 이 "설명 가능성"이 리뷰와 면접에서 결정적이다.
사례 연구 — "3만원 딱 맞췄는데 배송비가 붙어요"
앞의 조각들이 실제로 어떻게 이어지는지, 흔한 장면 하나로 따라가 보자.
1단계 — 제보. CS팀에서 문의가 넘어왔다.
"장바구니를 30,000원에 딱 맞춰서 주문했는데 배송비 3,000원이 붙었어요. 기획전 페이지에는 3만원 이상 무료라고 되어 있는데요."
2단계 — 재현하며 파티션과 경계를 세운다. 제보 한 건으로 바로 리포트를 쓰지 않는다. 금액 축의 경계 세 점을 먼저 찍어본다.
| 주문 금액 | 실제 결과 | 기대 |
|---|---|---|
| 29,999 | 배송비 3,000원 | 3,000원 ✅ |
| 30,000 | 배송비 3,000원 | 0원 ❌ |
| 30,001 | 배송비 0원 | 0원 ✅ |
30,000에서만 어긋난다. 전형적인 경계 포함 여부 결함이다. 이 표 하나로 "가끔 안 되는 것 같아요"가 "정확히 경계값에서만 재현됨"으로 바뀌었다.
3단계 — 가설을 세운다. 기획서는 "이상"이라고 했는데 코드는 >를 쓴 것 아닐까.
스테이지 3에서 배우는 코드 읽기를 조금만 쓰면 저장소에서 확인할 수 있다.
function getShippingFee(orderAmount) {
return orderAmount > 30000 ? 0 : 3000; // 기획서는 "이상" — >= 여야 한다
}가설이 맞았다. 여기서 리포트를 써도 되지만, 한 걸음 더 가는 것이 QA의 몫이다.
4단계 — 같은 경계에 걸린 다른 입력을 찾는다. orderAmount에 무엇이 들어오는지를 물으면
새 질문이 나온다. 쿠폰 3,000원을 적용해 33,000원 → 30,000원이 된 주문은 어느 금액으로
판정되는가? 확인해보니 쿠폰 적용 전 금액으로 판정되고 있었다. 그런데 기획서에는
"주문 금액"이라고만 적혀 있고 할인 전인지 후인지 정의가 없다.
이건 코드 결함이 아니라 명세 결함이다. >=로 고쳐도 이 질문은 남는다.
5단계 — 리포트를 쓴다. 두 가지를 분리해서 적는다.
제목: 주문 금액이 정확히 30,000원일 때 무료배송이 적용되지 않음
재현: 장바구니 합계를 정확히 30,000원으로 맞추고 결제 화면 진입
결과: 배송비 3,000원 부과 / 기대: 0원 (기획서 "3만원 이상 무료")
경계 확인: 29,999원 → 3,000원(정상), 30,000원 → 3,000원(결함), 30,001원 → 0원(정상)
원인 추정:
getShippingFee가> 30000으로 비교. "이상"이면>=.함께 결정 필요(명세): 무료배송 판정 기준 금액이 쿠폰·포인트 적용 전인지 후인지 기획서에 정의되어 있지 않습니다. 현재 구현은 적용 전 금액 기준입니다.
6단계 — 수정 확인(retest)과 회귀 범위를 정한다. "고쳤어요"라는 말을 받았을 때 30,000원 한 건만 확인하고 닫으면 절반만 한 것이다. 최소한 이만큼은 다시 본다.
- 경계 3점 재확인: 29,999 / 30,000 / 30,001 — 고치면서 반대쪽으로 밀리는 실수가 흔하다
(
>=를 넣으면서 상한 조건을 건드리는 식). - 같은 상수를 쓰는 다른 화면: 장바구니 요약, 주문서, 결제 완료, 주문 상세, 배송비 안내
문구. 같은
30000이 여러 곳에 흩어져 있다면 한 곳만 고쳐졌을 수 있다. - 연관 정책의 경계: 무료배송 쿠폰, 도서산간 추가배송비, 반품 시 배송비 환불 계산.
- 명세 결정 반영 여부: 4단계에서 제기한 "할인 전/후" 결정이 내려졌다면 그 케이스도 추가.
마지막 두 줄이 이 사례의 핵심이다. 경계값 하나에서 출발해 명세의 빈틈과 회귀 범위까지 끌어낸 것 — 이게 기법을 아는 QA와 그냥 눌러보는 QA의 차이다.
실무에서 왜 필요한가
자동화 테스트를 짤 때 파라미터라이즈 테스트에 어떤 값을 넣을지 고르는 근거가 바로 이 두 기법이다. "값을 몇 개 넣을까"가 아니라 "어떤 값이 서로 다른 코드 경로를 대표하는가"를 판단하는 게 이 두 기법의 실질적 쓸모다.
리뷰에서 살아남는 근거가 된다. 테스트 케이스 리뷰에서 가장 자주 나오는 질문은 "이 케이스 왜 필요해요?"와 "이거 빠진 거 아니에요?" 두 가지다. 앞의 질문에는 파티션으로, 뒤의 질문에는 경계 목록으로 답한다. 근거가 기법에 걸려 있으면 논쟁이 취향 싸움이 되지 않는다.
테스트 데이터 준비가 쉬워진다. 경계값이 정해지면 필요한 계정·상품·주문 데이터가 자동으로 결정된다. 반대로 기법 없이 만든 케이스는 "적당한 데이터"를 매번 새로 찾게 되고, 그 데이터가 사람마다 달라 재현이 안 되는 리포트가 나온다.
AI가 만든 TC를 검증할 때도 마찬가지다. AI에게 TC를 시키면 대체로 유효 케이스는 잘 만들고
경계와 비유효 조합을 빠뜨린다. 실제로 "나이 입력 TC 만들어줘"를 시켜보면 대부분
19와 65 자체를 빼먹거나, 넣더라도 >= 와 > 중 어느 쪽이 맞는지 요구사항에서 확인하지
않고 넘어간다. 기법을 모르면 "빠졌다"는 사실 자체를 알아채지 못한다. 그 확인이 사람의 몫이다.
채용시장에서의 위치
거의 모든 QA 채용공고에 "테스트 케이스 설계 기법 이해"가 들어간다. 면접에서 실제로 손으로 풀리는 단골 문제이기도 하다 — "이 입력 필드의 TC를 만들어 보세요" 같은 형태다. 이 모듈(테스트 설계 기법)이 이 커리큘럼 전체에서 가장 먼저, 가장 비중 있게 다뤄지는 이유가 여기 있다.
면접관이 이 문제로 보는 것은 케이스 개수가 아니라 순서다. 좋은 답변은 대개 이렇게 흘러간다. "먼저 입력을 파티션으로 나누고, 파티션이 맞닿는 지점을 경계로 잡습니다. 비유효는 하나씩 분리하고, 요구사항에 경계 포함 여부가 안 적혀 있으면 그건 케이스로 만들기 전에 확인이 필요한 항목으로 빼둡니다." 마지막 문장이 특히 인상에 남는다 — 명세의 빈틈을 발견하는 사람으로 보이기 때문이다.
한 단계 더 나오는 질문도 있다. "경계값 케이스가 너무 많아지면 어떻게 줄이시겠어요?" 여기서는 리스크 기반 판단(2-value vs 3-value)과 조합 기법(페어와이즈)을 언급할 수 있으면 좋다. "다 테스트합니다"는 좋은 답이 아니다.