개념
동등분할과 경계값 분석은 입력값 하나를 다뤘다. 이 레슨의 두 기법은 그보다 복잡한 상황 — 시간에 따라 상태가 바뀌는 경우와 여러 조건이 동시에 맞물리는 경우를 다룬다.
두 기법의 공통점을 먼저 짚어두면 이해가 빠르다. 둘 다 말로 쓰인 요구사항을 표로 옮기는 작업이다. 표로 옮기는 순간, 글로 읽을 때는 멀쩡해 보이던 요구사항에서 빈칸이 드러난다. 그 빈칸이 곧 테스트 케이스이자, 기획서에 아직 답이 없는 질문이다. 모듈 2에서 배운 "요구사항의 누락 찾기"가 여기서는 표라는 도구를 얻는다.
상태 전이 테스트 (State Transition Testing)
주문, 로그인, 게시글, 쿠폰처럼 정해진 상태들 사이를 오가는 대상에 쓴다. 이 기법을 쓰려면 네 가지를 구분할 줄 알아야 한다.
| 구성 요소 | 뜻 | 주문 예시 |
|---|---|---|
| 상태(State) | 시스템이 머물러 있는 모드. 대개 DB에 필드로 저장된다 | 결제대기, 배송중, 취소됨 |
| 이벤트(Event) | 상태를 바꾸려고 들어오는 자극 | 결제 성공, 주문 취소 버튼 클릭 |
| 전이(Transition) | "이 상태에서 이 이벤트를 받으면 저 상태로" 라는 규칙 | 결제대기 + 결제 성공 → 결제완료 |
| 가드 조건(Guard) | 전이가 일어나려면 추가로 만족해야 하는 조건 | 배송완료 + 반품 신청 → 단, 수령 7일 이내 |
"상태가 DB에 필드로 저장되는가" 가 이 기법을 쓸지 판단하는 가장 실용적인
신호다. order.status, user.is_locked, post.state 같은 필드가 보이면 그
기능은 거의 확실히 상태 전이 테스트의 대상이다.
실제 UI 흐름으로 보기 — 주문 상태
교과서의 3~4개짜리 예제와 달리, 실무의 주문 흐름은 취소와 반품이 섞이면서 금방 복잡해진다. 흔한 이커머스 주문을 그대로 옮겨보자.
결제대기 → 결제완료 → 배송준비 → 배송중 → 배송완료 그리고 취소는 출고 전까지만, 반품은 배송완료 후에만 가능하다.
이 한 줄짜리 요구사항을 전이표로 펼치면 이렇게 된다.
| 현재 상태 | 이벤트 | 다음 상태 | 가드 조건 |
|---|---|---|---|
| 결제대기 | 결제 성공 | 결제완료 | — |
| 결제대기 | 결제 시한 만료 | 취소됨 | 30분 경과 |
| 결제대기 | 주문 취소 | 취소됨 | — |
| 결제완료 | 상품 준비 시작 | 배송준비 | — |
| 결제완료 | 주문 취소 | 취소됨 | 전액 환불 발생 |
| 배송준비 | 송장 등록 | 배송중 | — |
| 배송준비 | 주문 취소 | 취소됨 | 출고 처리 전까지만 |
| 배송중 | 배송 완료 처리 | 배송완료 | — |
| 배송완료 | 반품 신청 | 반품접수 | 수령 후 7일 이내 |
| 반품접수 | 수거·검수 완료 | 반품완료 | — |
| 반품접수 | 반품 철회 | 배송완료 | — |
여기서 이미 요구사항에 없던 질문이 몇 개 나왔다. "출고 처리 전까지만"의 정확한 기준 시점은 언제인가? 송장 번호가 생긴 순간인가, 택배사가 집화한 순간인가? 반품 철회는 가능한가, 가능하다면 7일 카운터는 다시 시작되는가? 표를 채우다가 손이 멈추는 칸이 곧 기획자에게 물어야 할 항목이다.
상태-이벤트 매트릭스 — 빈칸이 진짜 목적이다
전이표는 "일어나는 일"의 목록이다. 여기서 한 걸음 더 나가면 훨씬 강력해진다. 상태를 세로, 이벤트를 가로로 놓은 격자를 만드는 것이다.
| 상태 \ 이벤트 | 결제 성공 | 주문 취소 | 송장 등록 | 배송완료 처리 | 반품 신청 |
|---|---|---|---|---|---|
| 결제대기 | 결제완료 | 취소됨 | — | — | — |
| 결제완료 | — | 취소됨 | — | — | — |
| 배송준비 | — | 취소됨 | 배송중 | — | — |
| 배송중 | — | — | — | 배송완료 | — |
| 배송완료 | — | — | — | — | 반품접수 |
| 취소됨 | — | — | — | — | — |
| 반품접수 | — | — | — | — | — |
| 반품완료 | — | — | — | — | — |
8개 상태 × 5개 이벤트 = 40칸 이다. 그중 채워진 칸은 8개뿐이고, 나머지 32칸은 전부 비어 있다. 여기가 핵심이다.
테스트 케이스 후보는 정상 전이 8개가 아니라 40개 다.
빈칸은 "일어나지 않는 일"이 아니라 "일어나면 안 되는 일" 이다. 그리고 일어나면 안 되는 일이 실제로 막히는지는 아무도 확인하지 않았다.
불가능한 전이야말로 테스트 대상이다
"배송완료 상태에서 취소를 시도하면?" 같은 비정상 전이(invalid transition) 를 테스트하는 게 이 기법의 가장 큰 실무 가치다. 왜 이런 결함이 그렇게 자주 살아남는지 이유가 분명하다.
화면에서 버튼을 숨기는 것과, 서버가 요청을 막는 것은 전혀 다른 일이다. 개발자는 보통 화면 쪽에서 "배송중이면 취소 버튼을 감춘다"를 구현하고, 그것으로 처리가 끝났다고 여긴다. 하지만 버튼이 안 보이는 것과 그 동작이 불가능한 것은 다르다. 버튼이 없어도 그 요청이 서버에 도달하는 경로는 여러 가지다.
| 경로 | 어떻게 벌어지나 |
|---|---|
| 화면이 낡았을 때 | 주문 상세를 열어둔 채 그 사이 상태가 바뀜. 사용자 화면에는 아직 취소 버튼이 남아 있다 |
| 탭을 두 개 열었을 때 | 한쪽에서 반품 신청, 다른 쪽에서 구매 확정 |
| 뒤로 가기 | 완료 화면에서 뒤로 가 이전 단계의 버튼을 다시 누름 |
| API 직접 호출 | 개발자 도구·Postman으로 취소 API를 그대로 호출 |
| 재시도·중복 클릭 | 응답이 느려서 결제 버튼을 두 번 누름 |
| 관리자·배치와의 충돌 | 사용자가 취소하는 동시에 배치가 출고 처리 |
마지막 두 줄이 특히 위험하다. 이 경로들은 정상 화면만 따라가는 테스트로는 절대 재현되지 않는다. 그래서 이 결함들은 대개 운영에 나가서, 고객센터 문의를 통해 발견된다.
비정상 전이를 테스트할 때 확인할 것은 "막혔다"만이 아니다. 세 가지를 함께 본다.
- 막히는가 — 상태가 바뀌지 않는가?
- 어떻게 막히는가 — 명확한 에러 메시지가 나오는가, 아니면 아무 반응 없이 조용히 무시되는가? 조용한 무시는 사용자가 성공했다고 오해하게 만든다.
- 부수 효과가 남지 않는가 — 상태는 그대로인데 환불만 나갔거나, 알림 메일만 발송됐거나, 재고만 복구된 경우가 실제로 많다. 가장 비싼 결함이 여기 있다.
커버리지를 정하는 법 — 0-switch와 1-switch
전이가 많아지면 "어디까지 테스트해야 충분한가"를 정해야 한다. 상태 전이 테스트에는 커버리지 단계가 있다.
| 단계 | 무엇을 보장하나 | 위 주문 예시에서 | 언제 쓰나 |
|---|---|---|---|
| 0-switch | 모든 상태 를 최소 한 번씩 방문 | 8개 상태를 지나는 경로 몇 개 | 최소한의 스모크 수준 |
| 1-switch | 모든 정상 전이 를 최소 한 번씩 실행 | 전이 11개를 모두 실행 | 실무의 기본선 |
| 비정상 전이 | 정의되지 않은 조합이 막히는지 확인 | 빈칸 32개 중 위험한 것부터 | 결제·환불이 걸린 기능은 필수 |
보통 1-switch가 실무의 기준선이다. 상태만 다 밟는 0-switch로는 "결제완료에서 취소" 같은 곁가지 전이가 통째로 빠지기 때문이다. 전이 수는 거의 항상 상태 수보다 많고, 그 차이가 바로 0-switch가 놓치는 양이다.
비정상 전이 32칸을 전부 테스트하는 건 현실적이지 않다. 우선순위는 이렇게 잡는다.
- 돈이 움직이는 칸부터 — 취소·환불·결제와 관련된 빈칸
- 되돌릴 수 없는 칸 — 배송 출고, 데이터 삭제, 포인트 소멸
- 시간 역행 칸 — 배송완료에서 결제대기로 돌아가는 것처럼 논리적으로 있을 수 없는 전이. 있을 수 없기 때문에 개발자가 아예 검사를 안 넣어둔 경우가 많다
한 걸음 더. 이 커리큘럼은 0-switch를 "모든 상태 방문", 1-switch를 "모든 전이 실행"으로 부른다. 문헌에 따라서는 N-switch를 "연속된 N+1개의 전이 시퀀스를 모두 실행"으로 정의하기도 한다. 용어보다 중요한 건 "상태만 볼 것이냐, 전이까지 볼 것이냐, 전이의 연쇄까지 볼 것이냐"라는 촘촘함의 단계 가 있다는 사실이다.
이 표를 손으로 그려두면 그대로 재활용된다. 이 모듈의 레슨 11(모델 기반 테스트) 에서는 지금 그린 것과 똑같은 상태 전이표를 도구가 읽을 수 있는 모델 로 넘겨, 경로 조합을 자동으로 순회하며 케이스를 생성하게 만든다. 즉 지금 하는 손 작업이 나중에 자동화의 입력이 된다.
결정 테이블 (Decision Table)
여러 개의 독립적인 조건이 동시에 맞물려 결과를 결정하는 규칙에 쓴다. 상태전이가 "시간 순서"를 다룬다면, 결정 테이블은 "그 순간의 조건 조합"을 다룬다.
규칙: 회원이고 쿠폰이 있으면 20% 할인. 회원이 아니지만 쿠폰이 있으면 10% 할인. 회원이지만 쿠폰이 없으면 5% 할인. 둘 다 아니면 할인 없음.
| 조건: 회원 여부 | Y | Y | N | N |
|---|---|---|---|---|
| 조건: 쿠폰 보유 | Y | N | Y | N |
| 결과: 할인율 | 20% | 5% | 10% | 0% |
만드는 순서는 정해져 있다. 막막할 때는 이 다섯 단계를 그대로 따라간다.
- 조건을 뽑는다. 요구사항 문장에서 "~이면", "~인 경우", "~를 제외하고"에 해당하는 부분을 전부 찾는다.
- 각 조건이 가질 수 있는 값을 정한다. Y/N이 기본이지만, 금액이나 등급처럼 구간이 있으면 동등분할(레슨 1)로 구간을 나눈다.
- 모든 조합을 나열한다. 조건이 N개고 전부 Y/N이면 2^N개다. 이 단계를 건너뛰면 안 된다 — 뒤에서 설명한다.
- 각 조합의 결과를 채운다. 여기서 채울 수 없는 칸이 나오면 그게 요구사항의 구멍이다.
- 불가능한 조합을 표시한다. "비회원인데 회원 전용 쿠폰 보유" 같은 조합은
논리적으로 성립하지 않으므로
불가로 표시하고 케이스를 만들지 않는다. 다만 시스템이 그 조합을 실제로 막는지는 별도로 확인해야 한다 — 여기가 결정 테이블과 비정상 전이 테스트가 만나는 지점이다.
조건 폭발 — 조건 하나가 표를 두 배로 만든다
결정 테이블의 가장 큰 실무 문제는 조합의 폭발이다. 조건이 하나 늘 때마다 규칙 수가 두 배가 된다.
| 조건 수 | 규칙 수 (전부 Y/N일 때) | 실감 |
|---|---|---|
| 2개 | 4 | 표가 한눈에 들어온다 |
| 3개 | 8 | 아직 괜찮다 |
| 4개 | 16 | 화면을 벗어나기 시작한다 |
| 5개 | 32 | 손으로 관리하기 어렵다 |
| 10개 | 1,024 | 불가능하다 |
조건 값이 Y/N이 아니라 세 구간이면 더 빨리 커진다. 조건 3개가 각각 3값·2값·3값이면 3 × 2 × 3 = 18개 다.
여기서 방향이 두 가지로 갈린다. 조합 자체를 줄이는 것(페어와이즈, 레슨 5)과 결과가 같은 규칙들을 병합하는 것(축약된 결정 테이블)이다. 이 레슨에서는 후자를 본다.
축약 — "don't care" 로 규칙을 병합하기
실제 예로 해보자. 할인 규칙이 이렇다.
- 골드 회원 은 금액·쿠폰과 무관하게 항상 15% 할인
- 실버 회원 은 쿠폰이 있으면 10%, 없으면 10만원 이상 구매 시에만 10%, 그 외에는 5%
- 일반 회원 은 쿠폰이 있고 3만원 이상 구매할 때만 5%, 나머지는 할인 없음
조건은 세 개다 — 회원등급(일반/실버/골드), 쿠폰 보유(Y/N), 장바구니 금액(3만 미만 /
3만~10만 / 10만 이상). 전체 조합은 3 × 2 × 3 = 18개 다. 18줄짜리 표를 그대로
쓰면 아무도 안 읽는다. 그런데 잘 보면 결과가 조건 하나와 무관한 구간 이 있다.
골드 회원은 쿠폰이 있든 없든, 금액이 얼마든 무조건 15%다. 그 6개 규칙은 한 줄로
합칠 수 있다. 이때 무관한 조건 칸에 —(don't care)를 적는다.
| 규칙 | 회원등급 | 쿠폰 보유 | 장바구니 금액 | 할인율 | 병합된 원래 규칙 수 |
|---|---|---|---|---|---|
| R1 | 골드 | — | — | 15% | 6 |
| R2 | 실버 | Y | — | 10% | 3 |
| R3 | 실버 | N | 10만 이상 | 10% | 1 |
| R4 | 실버 | N | 10만 미만 | 5% | 2 |
| R5 | 일반 | Y | 3만 이상 | 5% | 2 |
| R6 | 일반 | Y | 3만 미만 | 0% | 1 |
| R7 | 일반 | N | — | 0% | 3 |
병합된 규칙 수를 다 더하면 6 + 3 + 1 + 2 + 2 + 1 + 3 = 18 이다. 원래 조합이 빠짐없이 들어 있다는 뜻이다. 이 합계 검산이 축약 표에서 가장 중요한 절차다 — 합이 안 맞으면 어떤 조합을 통째로 빠뜨렸거나 두 번 센 것이다.
18줄이 7줄이 됐다. 여기서 오해하기 쉬운 세 가지를 못 박아두자.
첫째, — 는 "테스트하지 않는다"가 아니다. "어떤 값이어도 결과가 같다"는
주장 이다. 그 주장이 맞는지는 여전히 확인해야 한다. R1을 검증할 때는 대표값
하나(예: 골드 + 쿠폰 없음 + 5만원)로 케이스를 만들되, 위험이 크다면 don't care
안에서도 경계값 을 골라 잡는다 — 정확히 3만원, 정확히 10만원인 골드 회원.
레슨 1에서 배운 경계값 분석이 여기서 다시 쓰인다.
둘째, 결과가 같다고 아무 규칙이나 합쳐지지 않는다. R2와 R3는 둘 다 10%지만 합칠 수 없다. 두 규칙은 조건이 두 개(쿠폰, 금액) 다르기 때문이다. 조건이 정확히 하나만 다르고 그 조건의 모든 값을 덮을 때만 한 줄로 합쳐진다.
셋째, 축약은 반드시 전체 조합을 한 번 나열한 뒤에 한다. 처음부터 축약된 표를 그리면 "골드 회원이 쿠폰을 쓰면?" 같은 조합을 애초에 떠올리지 못한 채 넘어간다. 전체를 펼쳐서 빠뜨림을 확인하고, 그다음에 줄이는 것 이 순서다. 순서를 뒤집으면 결정 테이블을 쓰는 이유 자체가 사라진다.
결정 테이블에서 테스트 케이스로
축약된 표에서는 규칙 한 줄이 곧 테스트 케이스 하나 다. 이걸 결정 테이블 커버리지라고 부른다. 위 표라면 최소 7개의 케이스가 나온다.
| 케이스 | 입력 | 기대 결과 |
|---|---|---|
| TC-1 (R1) | 골드 / 쿠폰 없음 / 50,000원 | 15% 할인 = 42,500원 |
| TC-2 (R2) | 실버 / 쿠폰 있음 / 20,000원 | 10% 할인 = 18,000원 |
| TC-3 (R3) | 실버 / 쿠폰 없음 / 100,000원 | 10% 할인 = 90,000원 |
| TC-4 (R4) | 실버 / 쿠폰 없음 / 99,999원 | 5% 할인 (경계값) |
| TC-5 (R5) | 일반 / 쿠폰 있음 / 30,000원 | 5% 할인 (경계값) |
| TC-6 (R6) | 일반 / 쿠폰 있음 / 29,999원 | 할인 없음 (경계값) |
| TC-7 (R7) | 일반 / 쿠폰 없음 / 200,000원 | 할인 없음 |
TC-4·TC-5·TC-6에 주목하자. 규칙의 대표값을 아무거나 고르지 않고 구간의 경계에서 골랐다. 결정 테이블이 조합을 보장하고, 경계값 분석이 각 칸의 값을 고른다 — 두 기법은 경쟁 관계가 아니라 겹쳐 쓰는 관계다.
두 기법을 헷갈릴 때 — "시간과 순서가 있는가"
실무에서 가장 많이 받는 질문이 "이건 상태전이인가요, 결정테이블인가요"다. 판별 기준은 하나로 요약된다.
결과가 "지금 값"만으로 결정되면 결정 테이블, "여기까지 온 경로"까지 있어야 결정되면 상태 전이.
대비되는 두 예시로 보면 확실해진다.
예시 A — 계정 잠금
"비밀번호를 5회 연속 틀리면 계정이 잠기고, 30분 뒤 자동 해제된다. 중간에 한 번이라도 성공하면 실패 횟수는 0으로 초기화된다."
같은 입력("틀린 비밀번호")을 넣어도 결과가 다르다. 첫 번째 실패면 그냥 실패지만, 다섯 번째 실패면 계정이 잠긴다. 게다가 "중간에 성공하면 초기화"는 순서를 바꾸면 결과가 달라진다 는 뜻이다. → 상태 전이
예시 B — 대출 심사
"연소득 4천만원 이상이고 신용등급 3등급 이내면 승인. 둘 중 하나만 만족하면 조건부 승인. 둘 다 아니면 거절. 단, 기존 연체 이력이 있으면 무조건 거절."
조건 세 개를 어떤 순서로 확인하든 결과는 같다. 어제 신청했든 오늘 신청했든, 그 순간의 값 만 보면 결과가 정해진다. → 결정 테이블
판별할 때 던지는 질문은 세 개다.
| 질문 | 예 → 상태 전이 | 아니오 → 결정 테이블 |
|---|---|---|
| 같은 입력인데 이전 이력에 따라 결과가 달라지는가? | 5번째 실패는 잠금 | 소득·등급이 같으면 항상 같은 결과 |
| "지금 무슨 상태인가"를 저장하는 필드가 있는가? | failedCount, isLocked | 심사 시점에 계산만 함 |
| 순서를 바꾸면 결과가 달라지는가? | 실패-성공-실패 vs 실패-실패-실패 | 조건 확인 순서는 무관 |
두 기법을 함께 쓰는 경우
현실에서는 둘 중 하나만 쓰는 경우가 오히려 드물다. 상태 전이의 가드 조건이 곧 결정 테이블 인 경우가 아주 흔하다.
앞의 주문 예시에서 배송완료 → 반품접수 전이 하나를 확대해보자.
반품 신청은 수령 후 7일 이내 이고, 신선식품이 아니며 , 구매 확정을 누르지 않은 경우에만 가능하다.
전이는 하나인데 가드 조건이 세 개다. 이 하나의 화살표를 결정 테이블로 펼치면 2 × 2 × 2 = 8가지가 나온다.
| 7일 이내 | 신선식품 아님 | 구매확정 안 함 | 결과 |
|---|---|---|---|
| Y | Y | Y | 반품접수로 전이 |
| Y | Y | N | 거부 — "구매 확정된 주문입니다" |
| Y | N | — | 거부 — "신선식품은 반품 불가입니다" |
| N | — | — | 거부 — "반품 기간이 지났습니다" |
여기서도 축약이 쓰였다(—). 그리고 거부 사유가 서로 다르다는 점 이 중요하다.
세 조건이 동시에 실패할 때 어떤 메시지가 나가야 하는지는 요구사항에 대개 안 적혀
있다. 표를 그렸기 때문에 그 질문이 나왔다.
정리하면 이렇게 겹쳐 쓴다 — 상태 전이표로 "어떤 화살표가 있는가"를 잡고, 각 화살표의 가드 조건을 결정 테이블로 펼친다.
사례 연구 — "취소한 적 없는 주문이 취소됐어요"
지금까지의 조각을 하나의 흐름으로 이어보자. CS팀에서 이런 제보가 왔다.
"고객이 배송 중인 주문을 취소했다는데, 환불은 나갔고 상품은 그대로 배송됐습니다. 주문 상태는 '취소됨'인데 송장은 살아 있어요."
1단계 — 재현 시도, 그리고 실패. 주문을 하나 만들어 배송중으로 만든 뒤 상세 화면을 열었다. 취소 버튼이 아예 없다. 화면상으로는 취소할 방법이 없다. 여기서 대부분 "재현 불가"로 닫는다.
2단계 — 표를 그린다. 값을 바꿔가며 눌러보는 대신 상태-이벤트 매트릭스를
그렸다. 8 × 5 = 40칸 중 배송중 + 주문 취소 칸이 비어 있다. "비어 있다"는 건
"막혀 있다"는 뜻이 아니라 "아무도 정의하지 않았다"는 뜻이다.
3단계 — 가설을 세운다. 화면은 버튼을 숨기지만, 서버가 그 요청을 막는지는 별개다. 그렇다면 버튼이 아직 남아 있는 화면 에서 누르면 어떻게 될까? 상태가 바뀌는 순간 사용자가 이미 화면을 열어두고 있었다면 정확히 그 상황이 된다.
4단계 — 검증한다.
- 주문을 배송준비 상태로 만들고, 사용자 화면에서 주문 상세를 연다(취소 버튼 있음).
- 화면을 새로고침하지 않은 채, 관리자에서 송장을 등록해 배송중으로 바꾼다.
- 열어둔 화면에서 취소 버튼을 누른다.
결과 — 요청은 성공(200)하고, 환불이 실행되고, 주문 상태가 취소됨으로 바뀐다. 그런데 송장은 이미 택배사로 넘어간 뒤라 배송은 그대로 진행된다. 재현 성공.
5단계 — 리포트를 쓴다. 모듈 6에서 배운 형식으로.
제목: 배송중 주문에 취소 API가 허용되어 환불 후에도 배송이 진행됨
재현: 배송준비 상태에서 주문 상세를 연 뒤(화면 미갱신), 관리자에서 송장 등록 → 열어둔 화면의 취소 버튼 클릭
결과: 취소 성공(200), 전액 환불 실행, 상태
CANCELED/ 송장은 유효 기대: 출고 이후 취소 요청은 거부되고 "이미 출고되어 취소할 수 없습니다" 안내원인 추정: 취소 버튼 노출은 화면에서만 제어되고, 서버 측 취소 API에 현재 상태 검증이 없는 것으로 보임(상태-이벤트 매트릭스의
배송중 + 취소칸이 미정의)함께 확인 필요: ① 배송완료 상태에서 같은 방법 ② 이미 취소된 주문 재취소 시 중복 환불 여부 ③ 반품완료 후 재반품 ④ 취소와 출고 배치의 동시 실행
마지막 줄이 이 사례의 핵심이다. 이 결함은 값을 바꿔가며 찾아낸 게 아니라 표의 빈칸이 알려준 것 이다. 그리고 빈칸이 하나가 아니었기 때문에, 같은 원인을 가진 다른 결함까지 한 번에 짚어줄 수 있었다. 개발자가 이 리포트를 받으면 취소 API만 고치는 게 아니라 상태 검증을 어디에 둬야 하는지 를 다시 보게 된다.
화면에 안 보이는 상태를 찾아내기
상태 전이표를 그릴 때 가장 자주 빠지는 것은 UI에 이름이 없는 상태 다. 화면에 표시되지 않으니 기획서에도 없고, 그래서 테스트에서도 빠진다.
| 자주 빠지는 상태 | 어디서 생기나 | 안 다루면 벌어지는 일 |
|---|---|---|
| 처리중(in-flight) | 결제 승인 요청을 보내고 응답을 기다리는 몇 초 | 사용자가 두 번 누르면 이중 결제 |
| 부분 취소·부분 환불 | 여러 상품 중 일부만 취소 | 남은 금액·배송비 계산이 어긋남 |
| 만료됨 | 쿠폰·세션·인증코드의 유효기간 경과 | 만료된 쿠폰이 계속 적용됨 |
| 소프트 삭제 | 삭제 표시만 하고 데이터는 남겨둠 | 삭제한 게시글이 검색 결과에 나옴 |
| 구매 확정 | 배송완료 후 일정 기간 경과 또는 사용자 확정 | 확정 이후에도 반품이 가능해짐 |
| 실패 후 재시도 대기 | 결제 실패, 알림 발송 실패 | 재시도가 무한 반복되거나 아예 안 됨 |
이 상태들을 찾아내는 실용적인 방법이 두 가지 있다. 첫째, DB의 상태 컬럼이 가질 수 있는 값 목록을 개발자에게 직접 물어본다. 기획서의 상태 개수와 코드의 상태 개수가 다른 경우가 아주 흔하고, 그 차이가 곧 테스트되지 않은 영역이다. 둘째, "이 동작이 실패하면 어떤 상태에 남는가" 를 모든 전이마다 물어본다. 성공 경로는 기획서에 있지만 실패 후 남는 상태는 대개 없다.
실무에서 왜 필요한가
값 기반 기법으로는 안 잡히는 결함이 있다
상태 관련 결함(취소된 주문을 다시 결제 시도, 이미 삭제된 게시글을 수정 시도)은 동등분할·경계값으로는 안 잡힌다 — 그 기법들은 "값 하나"를 보지 "이전에 어떤 상태를 거쳐왔는가" 를 안 보기 때문이다. 앞의 사례 연구에서 본 결함도 마찬가지다. 입력값은 완벽히 정상이었고, 문제는 그 입력이 도착한 시점의 상태 에 있었다.
표가 요구사항의 빈칸을 드러낸다
결정 테이블도 같은 역할을 한다. 조건이 2~3개만 넘어가도 글로 풀어 쓴 요구사항에서는 조합 하나가 조용히 빠지기 쉽다. "쿠폰 두 개를 동시에 쓰면?", "골드 회원이 쿠폰을 쓰면 15%인가 20%인가?" 같은 질문은 대개 표를 그리는 과정에서 발견된다. 이건 모듈 2에서 배운 "누락 찾기"가 표라는 도구로 구체화된 것이다.
발견 시점도 중요하다. 이 표는 코드가 한 줄도 없을 때 그릴 수 있다. 요구사항 리뷰 단계에서 빈칸을 발견하면 문장 한 줄로 끝나지만, 개발이 끝난 뒤에 발견하면 상태 필드 설계 자체를 손대야 할 수도 있다. 모듈 1에서 본 결함 수정 비용 곡선이 여기서도 그대로 적용된다.
개발자·기획자와 같은 그림을 보게 된다
상태 전이표는 QA만 쓰는 문서가 아니다. 개발자는 이 표를 그대로 상태 검증 로직으로 옮길 수 있고, 기획자는 자기가 쓴 문장에 무엇이 빠졌는지 한눈에 본다. "이 칸은 어떻게 되나요?" 라는 질문은 "요구사항이 모호합니다"보다 훨씬 답하기 쉬운 질문이라, 실제로 답을 받아낼 확률이 높다. 모듈 2에서 배운 "질문으로 되돌리기"의 가장 구체적인 형태가 이 표다.
회귀 테스트와 자동화의 뼈대가 된다
한 번 그려둔 상태 전이표는 기능이 바뀌기 전까지 계속 쓰인다. 새 상태나 새 이벤트가 추가될 때 표에 행이나 열을 하나 더하면, 그 순간 새로 생긴 빈칸들이 그대로 회귀 테스트 대상 이 된다. "배송 보류" 상태 하나를 추가하면 검토할 칸이 5개 늘어난다는 사실이 표에서 바로 보인다.
같은 표가 자동화의 입력도 된다. 레슨 11의 모델 기반 테스트는 이 표를 모델로 읽어 경로를 자동 생성하고, 스테이지 4의 E2E 자동화에서는 각 전이가 하나의 테스트 시나리오가 된다. 손으로 그리는 이 작업이 뒤 스테이지 전체의 재료다.
덧붙이면, 이 두 기법은 ISTQB Foundation 시험과 QA 면접의 단골 주제이기도 하다. 면접에서는 대개 "상태 전이 테스트와 결정 테이블의 차이"를 묻는데, 정의를 외워 말하기보다 "시간·순서가 있으면 상태 전이, 그 순간의 조건 조합이면 결정 테이블" 이라는 판별 기준과 함께, 비정상 전이를 테스트하는 것이 이 기법의 핵심 가치 라고 답하면 실무 경험이 있는 사람으로 읽힌다.