본문으로 건너뛰기
중급약 24분

명세 기반 기법 (2) 상태전이와 결정테이블

시간에 따라 상태가 바뀌는 시스템은 상태전이로, 여러 조건이 동시에 맞물리는 규칙은 결정테이블로 설계한다.

  • #상태전이
  • #결정테이블
  • #테스트설계

개념

동등분할과 경계값 분석은 입력값 하나를 다뤘다. 이 레슨의 두 기법은 그보다 복잡한 상황 — 시간에 따라 상태가 바뀌는 경우와 여러 조건이 동시에 맞물리는 경우를 다룬다.

두 기법의 공통점을 먼저 짚어두면 이해가 빠르다. 둘 다 말로 쓰인 요구사항을 표로 옮기는 작업이다. 표로 옮기는 순간, 글로 읽을 때는 멀쩡해 보이던 요구사항에서 빈칸이 드러난다. 그 빈칸이 곧 테스트 케이스이자, 기획서에 아직 답이 없는 질문이다. 모듈 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를 그대로 호출
재시도·중복 클릭응답이 느려서 결제 버튼을 두 번 누름
관리자·배치와의 충돌사용자가 취소하는 동시에 배치가 출고 처리

마지막 두 줄이 특히 위험하다. 이 경로들은 정상 화면만 따라가는 테스트로는 절대 재현되지 않는다. 그래서 이 결함들은 대개 운영에 나가서, 고객센터 문의를 통해 발견된다.

비정상 전이를 테스트할 때 확인할 것은 "막혔다"만이 아니다. 세 가지를 함께 본다.

  1. 막히는가 — 상태가 바뀌지 않는가?
  2. 어떻게 막히는가 — 명확한 에러 메시지가 나오는가, 아니면 아무 반응 없이 조용히 무시되는가? 조용한 무시는 사용자가 성공했다고 오해하게 만든다.
  3. 부수 효과가 남지 않는가 — 상태는 그대로인데 환불만 나갔거나, 알림 메일만 발송됐거나, 재고만 복구된 경우가 실제로 많다. 가장 비싼 결함이 여기 있다.

커버리지를 정하는 법 — 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% 할인. 둘 다 아니면 할인 없음.

조건: 회원 여부YYNN
조건: 쿠폰 보유YNYN
결과: 할인율20%5%10%0%

만드는 순서는 정해져 있다. 막막할 때는 이 다섯 단계를 그대로 따라간다.

  1. 조건을 뽑는다. 요구사항 문장에서 "~이면", "~인 경우", "~를 제외하고"에 해당하는 부분을 전부 찾는다.
  2. 각 조건이 가질 수 있는 값을 정한다. Y/N이 기본이지만, 금액이나 등급처럼 구간이 있으면 동등분할(레슨 1)로 구간을 나눈다.
  3. 모든 조합을 나열한다. 조건이 N개고 전부 Y/N이면 2^N개다. 이 단계를 건너뛰면 안 된다 — 뒤에서 설명한다.
  4. 각 조합의 결과를 채운다. 여기서 채울 수 없는 칸이 나오면 그게 요구사항의 구멍이다.
  5. 불가능한 조합을 표시한다. "비회원인데 회원 전용 쿠폰 보유" 같은 조합은 논리적으로 성립하지 않으므로 불가 로 표시하고 케이스를 만들지 않는다. 다만 시스템이 그 조합을 실제로 막는지는 별도로 확인해야 한다 — 여기가 결정 테이블과 비정상 전이 테스트가 만나는 지점이다.

조건 폭발 — 조건 하나가 표를 두 배로 만든다

결정 테이블의 가장 큰 실무 문제는 조합의 폭발이다. 조건이 하나 늘 때마다 규칙 수가 두 배가 된다.

조건 수규칙 수 (전부 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실버N10만 이상10%1
R4실버N10만 미만5%2
R5일반Y3만 이상5%2
R6일반Y3만 미만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일 이내신선식품 아님구매확정 안 함결과
YYY반품접수로 전이
YYN거부 — "구매 확정된 주문입니다"
YN—거부 — "신선식품은 반품 불가입니다"
N——거부 — "반품 기간이 지났습니다"

여기서도 축약이 쓰였다(—). 그리고 거부 사유가 서로 다르다는 점 이 중요하다. 세 조건이 동시에 실패할 때 어떤 메시지가 나가야 하는지는 요구사항에 대개 안 적혀 있다. 표를 그렸기 때문에 그 질문이 나왔다.

정리하면 이렇게 겹쳐 쓴다 — 상태 전이표로 "어떤 화살표가 있는가"를 잡고, 각 화살표의 가드 조건을 결정 테이블로 펼친다.

사례 연구 — "취소한 적 없는 주문이 취소됐어요"

지금까지의 조각을 하나의 흐름으로 이어보자. CS팀에서 이런 제보가 왔다.

"고객이 배송 중인 주문을 취소했다는데, 환불은 나갔고 상품은 그대로 배송됐습니다. 주문 상태는 '취소됨'인데 송장은 살아 있어요."

1단계 — 재현 시도, 그리고 실패. 주문을 하나 만들어 배송중으로 만든 뒤 상세 화면을 열었다. 취소 버튼이 아예 없다. 화면상으로는 취소할 방법이 없다. 여기서 대부분 "재현 불가"로 닫는다.

2단계 — 표를 그린다. 값을 바꿔가며 눌러보는 대신 상태-이벤트 매트릭스를 그렸다. 8 × 5 = 40칸 중 배송중 + 주문 취소 칸이 비어 있다. "비어 있다"는 건 "막혀 있다"는 뜻이 아니라 "아무도 정의하지 않았다"는 뜻이다.

3단계 — 가설을 세운다. 화면은 버튼을 숨기지만, 서버가 그 요청을 막는지는 별개다. 그렇다면 버튼이 아직 남아 있는 화면 에서 누르면 어떻게 될까? 상태가 바뀌는 순간 사용자가 이미 화면을 열어두고 있었다면 정확히 그 상황이 된다.

4단계 — 검증한다.

  1. 주문을 배송준비 상태로 만들고, 사용자 화면에서 주문 상세를 연다(취소 버튼 있음).
  2. 화면을 새로고침하지 않은 채, 관리자에서 송장을 등록해 배송중으로 바꾼다.
  3. 열어둔 화면에서 취소 버튼을 누른다.

결과 — 요청은 성공(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 면접의 단골 주제이기도 하다. 면접에서는 대개 "상태 전이 테스트와 결정 테이블의 차이"를 묻는데, 정의를 외워 말하기보다 "시간·순서가 있으면 상태 전이, 그 순간의 조건 조합이면 결정 테이블" 이라는 판별 기준과 함께, 비정상 전이를 테스트하는 것이 이 기법의 핵심 가치 라고 답하면 실무 경험이 있는 사람으로 읽힌다.

실습 과제

과제 1 — 상태 전이표 그리기 (10분)

"게시글" 기능의 상태가 임시저장 → 게시됨 → 삭제됨 이렇게 세 가지가 있고, 임시저장에서 바로 삭제도 가능하다고 하자. 상태 전이표를 만들고, 비정상 전이(막혀야 하는 조합) 를 최소 2개 찾아 적는다.

해설 보기

정상 전이는 네 개다.

현재 상태이벤트다음 상태
임시저장게시게시됨
임시저장삭제삭제됨
게시됨수정게시됨 (자기 자신으로)
게시됨삭제삭제됨

비정상 전이 예시 — 삭제됨 상태에서 수정 , 삭제됨 상태에서 게시 , 게시됨 상태에서 다시 게시 (중복 게시), 삭제된 글에 댓글 작성 .

"게시됨 + 수정 → 게시됨"처럼 자기 자신으로 돌아오는 전이 를 찾아냈다면 좋다. 상태는 안 바뀌지만 데이터는 바뀌는 전이이고, 실무에서 자주 빠뜨린다.

과제 2 — 매트릭스의 빈칸 공략하기 (15분)

본문의 주문 상태-이벤트 매트릭스(8상태 × 5이벤트 = 40칸)를 보고, 빈칸 32개 중 가장 먼저 테스트해야 할 5칸 을 고른다. 각 칸마다 ①왜 위험한지 ②어떻게 시도할 것인지(화면으로 불가능하다면 어떤 경로로) ③기대 결과가 무엇인지를 적는다.

해설 보기

우선순위 기준은 "돈이 움직이는가 / 되돌릴 수 없는가 / 시간을 역행하는가"다. 예시 다섯 칸이다.

칸왜 위험한가시도 경로기대 결과
배송중 + 주문 취소환불은 나가고 배송은 진행낡은 화면·API 직접 호출거부 + 명확한 안내
취소됨 + 주문 취소중복 환불취소 요청 두 번 연속두 번째는 거부, 환불 1회만
배송완료 + 결제 성공시간 역행. 검증이 아예 없을 가능성결제 콜백 재전송상태 변화 없음
반품완료 + 반품 신청이중 환불반품 완료 후 재신청거부
결제대기 + 배송완료 처리결제 없이 배송 확정관리자 화면·배치거부

세 번째가 특히 중요하다. 결제 게이트웨이가 콜백을 재전송하는 일은 실제로 일어나며, 이 칸을 검사하지 않은 시스템은 상태가 통째로 뒤로 돌아간다.

과제 3 — 결정 테이블 만들기 (12분)

다음 규칙으로 결정 테이블을 만든다: "첫 구매이고 5만원 이상 구매하면 무료배송. 첫 구매가 아니어도 10만원 이상이면 무료배송. 그 외에는 배송비 3천원." (조건 두 개: 첫 구매 여부, 구매 금액 — 구매 금액은 "5만원 미만 / 5만원~10만원 / 10만원 이상" 세 구간으로 나눠본다.)

해설 보기

전체 조합은 2 × 3 = 6개다. 먼저 전부 펼친다.

첫 구매금액결과
Y5만 미만3,000원
Y5만~10만무료
Y10만 이상무료
N5만 미만3,000원
N5만~10만3,000원
N10만 이상무료

축약하면 네 줄이 된다 — Y / 5만 이상 → 무료, Y / 5만 미만 → 3,000원, N / 10만 이상 → 무료, N / 10만 미만 → 3,000원. 병합 수는 2+1+1+2 = 6으로 검산이 맞는다.

테스트 값은 경계에서 고른다 — 49,999 / 50,000 / 99,999 / 100,000. 특히 첫 구매 + 정확히 50,000원 과 재구매 + 정확히 100,000원 이 핵심 케이스다.

여기서 나와야 할 질문도 있다. "첫 구매"의 기준은 결제 완료인가 주문 생성인가? 이전 주문을 전부 취소했으면 다시 첫 구매인가? 표를 그리면 이런 질문이 따라온다.

과제 4 — 축약해보기 (15분)

아래 규칙의 결정 테이블을 만들고 don't care(—)로 축약한 뒤, 병합 수 합계 검산 까지 한다.

"관리자는 모든 게시글을 수정·삭제할 수 있다. 일반 사용자는 자기가 쓴 글만, 그것도 게시 후 24시간 이내에만 수정할 수 있다. 삭제는 자기 글이면 시간과 무관하게 가능하다."

조건 세 개로 잡는다 — 역할(관리자/일반), 작성자 본인 여부(Y/N), 24시간 이내(Y/N). 결과는 수정 가능 여부와 삭제 가능 여부 두 개 다.

해설 보기

전체 조합은 2 × 2 × 2 = 8개다. 축약하면 이렇게 된다.

규칙역할본인 글24h 이내수정삭제병합 수
R1관리자——가능가능4
R2일반YY가능가능1
R3일반YN불가가능1
R4일반N—불가불가2

합계 4 + 1 + 1 + 2 = 8. 검산이 맞는다.

R3가 이 과제의 핵심이다. 결과가 두 개일 때는 한 조건이 한쪽 결과에만 영향을 주는 경우가 생긴다. "24시간"은 수정에만 걸리고 삭제에는 안 걸리므로, R2와 R3는 삭제 결과가 같아도 수정 결과가 달라 합칠 수 없다.

그리고 경계값 — 정확히 24시간 0분 0초일 때는 수정이 되는가? "이내"라면 되어야 한다. 이 질문이 나왔다면 레슨 1과 이 레슨을 겹쳐 쓴 것이다.

과제 5 — 기법 고르기 (8분)

아래 요구사항 각각에 어느 기법이 맞는지 고르고, 판별 질문 세 개 중 무엇을 근거로 삼았는지 적는다.

  1. 배송비는 지역(도서산간/일반)과 무게(5kg 미만/이상)에 따라 결정된다.
  2. 인증번호는 발송 후 3분간 유효하고, 3회 틀리면 재발송해야 한다.
  3. 쿠폰은 발급 → 사용가능 → 사용됨 순서로 진행되며, 중간에 만료될 수 있다.
  4. 회원 등급은 최근 3개월 구매액과 구매 횟수로 산정된다.
  5. 장바구니에 담은 상품이 품절되면 결제 단계에서 제외된다.
해설 보기
  1. 결정 테이블 — 2 × 2 = 4가지. 순서와 무관하고 그 순간의 값만 본다.
  2. 상태 전이 — "3회 틀리면"이 누적 이력이다. 게다가 "3분"이라는 시간 조건이 있어 만료 상태가 존재한다. 실패 횟수를 세는 필드가 DB에 있을 것이다.
  3. 상태 전이 — 상태 이름이 그대로 주어져 있다. 비정상 전이(사용됨 상태에서 재사용, 만료된 쿠폰 사용)가 핵심 테스트 대상이다.
  4. 결정 테이블 — 구매액 구간 × 횟수 구간의 조합이다. 다만 "최근 3개월"이라 시간이 등장하는데, 이건 입력값을 계산하는 조건 이지 시스템의 상태가 아니다. 이 구분이 이 과제에서 가장 헷갈리는 지점이다.
  5. 둘 다 — 상품 재고에 상태(판매중/품절)가 있고, 장바구니 담기 이후 품절로 전이되는 순간이 문제의 지점이다. 동시에 "품절 + 쿠폰 적용 + 최소 주문금액 미달" 같은 조건 조합도 따져야 한다. 본문의 "가드 조건을 결정 테이블로 펼치기"에 해당한다.

4번과 5번을 맞췄다면 두 기법의 경계를 제대로 잡은 것이다.

자가 체크리스트

  • 상태 전이 테스트와 결정 테이블의 차이를 "시간·순서 vs 그 순간의 조건 조합"으로 설명할 수 있다
  • 상태·이벤트·전이·가드 조건 네 가지를 구분해서 말할 수 있다
  • 상태-이벤트 매트릭스를 그리고, 빈칸이 왜 테스트 대상인지 설명할 수 있다
  • 화면에서 버튼을 숨기는 것과 서버가 요청을 막는 것이 왜 다른지 설명할 수 있다
  • 비정상 전이를 시도할 수 있는 경로를 세 가지 이상 댈 수 있다 (낡은 화면, API 직접 호출 등)
  • 비정상 전이 테스트에서 "막혔는가" 외에 무엇을 더 확인해야 하는지 안다 (에러 메시지, 부수 효과)
  • 0-switch와 1-switch의 차이를 설명하고, 왜 1-switch가 실무 기준선인지 말할 수 있다
  • 결정 테이블을 만드는 다섯 단계를 순서대로 적용할 수 있다
  • 조건이 N개면 규칙이 2^N개로 늘어난다는 것과, 그 대응책 두 가지(축약·페어와이즈)를 안다
  • don't care(—)로 규칙을 병합하고, 병합 수 합계로 검산할 수 있다
  • — 가 "테스트하지 않는다"는 뜻이 아니라는 것을 설명할 수 있다
  • 결과가 같아도 조건이 두 개 이상 다르면 병합할 수 없다는 것을 안다
  • 축약을 하기 전에 전체 조합을 한 번 펼쳐야 하는 이유를 설명할 수 있다
  • 축약된 규칙 한 줄을 테스트 케이스로 옮기면서 대표값을 경계에서 고를 수 있다
  • 상태 전이의 가드 조건을 결정 테이블로 펼치는 방식을 예로 들 수 있다
  • 화면에 안 보이는 상태(처리중, 부분환불, 만료, 소프트 삭제)를 찾아내는 방법을 안다

흔한 실수

  • 정상 전이만 테스트하고 비정상 전이를 빼먹는다. "배송완료 후 다시 취소를 누르면?"처럼, 표에 없는 칸이야말로 테스트해야 할 대상이다. 매트릭스를 그리면 정상 전이보다 빈칸이 훨씬 많다는 사실이 눈으로 보인다.
  • 화면에 버튼이 없으니 불가능하다고 판단한다. 버튼을 숨기는 것과 서버가 막는 것은 다른 일이다. 낡은 화면, 뒤로 가기, 두 번째 탭, API 직접 호출로 그 요청은 얼마든지 도달한다.
  • "막혔다"까지만 확인하고 부수 효과를 안 본다. 상태는 그대로인데 환불만 나갔거나 알림 메일만 발송된 경우가 실제로 가장 비싼 결함이다.
  • 상태 전이표를 정상 흐름(happy path)만으로 그린다. 실패·만료·타임아웃으로 들어가는 상태가 빠지면 표 자체가 반쪽이 된다. 모든 전이마다 "이게 실패하면 어떤 상태에 남는가"를 물어야 한다.
  • 화면에 이름이 없는 상태를 놓친다. 처리중, 부분 취소, 소프트 삭제, 구매 확정은 UI에 안 보이지만 DB에는 있다. 상태 컬럼의 값 목록을 개발자에게 직접 물어보는 게 가장 빠르다.
  • 결정 테이블을 처음부터 축약된 형태로 그린다. 그러면 애초에 떠올리지 못한 조합이 통째로 빠진다. 전부 펼쳐서 확인한 다음에 줄이는 것 이 순서다.
  • 축약하면서 병합 수 검산을 안 한다. 합계가 원래 조합 수와 맞지 않으면 어딘가를 빠뜨렸거나 중복해서 센 것이다. 검산 한 줄이 표 전체의 신뢰도를 만든다.
  • don't care를 "테스트 안 해도 되는 칸"으로 오해한다. — 는 "값과 무관하게 결과가 같다"는 주장일 뿐이고, 그 주장이 맞는지는 대표값(가능하면 경계값)으로 확인해야 한다.
  • 결과가 같다는 이유로 아무 규칙이나 합친다. 조건이 정확히 하나만 다르고 그 조건의 모든 값을 덮을 때만 병합된다.
  • 결정 테이블의 조건을 너무 잘게 쪼개 표가 감당 안 되게 커진다. 조건이 4개를 넘어가면(16가지 조합) 축약으로도 안 줄어들 때가 있다. 그때는 조합 테스트 기법(레슨 5의 페어와이즈)으로 넘어가는 게 낫다.
  • 시간이 걸린 문제를 결정 테이블로 풀려고 한다. "3회 실패하면 잠금"처럼 이력이 결과를 바꾸는 규칙은 결정 테이블로 표현되지 않는다. 조건 칸에 "지금까지 몇 번 실패했는가"를 넣고 싶어진다면, 그건 상태 전이로 가라는 신호다.
  • 상태 다이어그램만 그리고 실제 테스트 케이스로 옮기지 않는다. 다이어그램은 이해를 돕는 도구고, 실제로는 각 전이를 실행하는 테스트 케이스가 있어야 한다.

참고 자료

  • ISTQB Foundation Level Syllabus — State Transition Testing — 0-switch/1-switch 커버리지 개념 포함
  • ISTQB Glossary — Decision Table Testing — 결정 테이블의 공식 정의와 축약(collapsed) 표기
  • ISO/IEC/IEEE 29119-4 — 테스트 기법 표준. 상태 전이·결정 테이블의 커버리지 항목이 정의되어 있다
  • Lee Copeland, A Practitioner's Guide to Software Test Design — 상태 전이표와 결정 테이블을 실무 예제로 다루는 고전
  • 이 모듈 레슨 1(동등분할·경계값) — 결정 테이블의 각 칸에 넣을 값을 고를 때 함께 쓴다
  • 이 모듈 레슨 5(페어와이즈) — 조건이 4개를 넘어 축약으로도 감당이 안 될 때의 다음 단계
  • 이 모듈 레슨 11(모델 기반 테스트) — 이 레슨에서 손으로 그린 상태 전이표를 도구가 읽는 모델로 삼아 경로를 자동 생성한다

낯선 말 풀이

이 레슨에 나온 약어입니다. 본문에서도 처음 나올 때 점선으로 표시해 두었습니다 (제목과 링크 글자는 그대로 둡니다).

DB — 데이터베이스
데이터를 넣어 두고 꺼내 쓰는 저장소. 보통 표 여러 개가 서로 연결된 형태다.
UI — 사용자 인터페이스
사람이 직접 보고 누르는 부분 — 화면, 버튼, 입력창.
API — 응용 프로그램 인터페이스
프로그램끼리 주고받는 창구. 사람이 화면 버튼을 누르는 대신, 프로그램이 정해진 주소로 요청을 보내 답을 받는다.
CS — 고객 지원 · 칩 선택 신호
이 커리큘럼에서 두 가지로 쓰인다. ① 고객 문의와 불만을 직접 받는 조직 ② 여러 부품이 한 선을 나눠 쓸 때 "지금은 네 차례" 라고 하나를 고르는 신호. 앞뒤에 문의·고객이 있으면 ①, 통신·배선 이야기면 ②다.
QA — 품질보증
만든 것이 쓸 만한지 확인하고, 애초에 덜 망가지게 만드는 방법을 정하는 일. "테스트"는 그 일의 일부다.
E2E — 끝에서 끝까지
화면 한 조각이 아니라 사용자가 밟는 전체 흐름을 처음부터 끝까지 한 번에 돌려 보는 시험.
ISTQB — 국제 소프트웨어 테스팅 자격 위원회
테스터 자격 시험을 만들고 용어를 정리하는 국제 단체.
ISO/IEC/IEEE — 세 단체 공동 표준
세 국제 단체가 함께 낸 표준이라는 표시.

용어집 전체 보기

이 레슨의 실습

실습은 별도 저장소에서 진행합니다. 새 탭으로 열립니다.