개념
레슨 3의 표를 "실행 가능한 모델"로
레슨 3에서 우리는 주문 상태 전이표를 손으로 그렸다. 결제대기 → 결제완료 → 배송준비 → 배송중 → 배송완료라는 흐름에 취소와 반품이 얽히면서, 상태 8개에 정상 전이 11개가 나왔다. 그리고 그것을 상태-이벤트 매트릭스로 펼치자 8 × 5 = 40칸 중 32칸이 빈칸이라는 사실이 눈에 보였다.
여기까지 오면 자연스럽게 다음 질문이 생긴다.
표를 그리는 것까지는 사람이 했다. 그 표를 보고 경로를 골라내는 일까지 사람이 해야 하는가?
상태가 몇 개 안 될 때는 사람이 표를 보고 어떤 전이를 테스트할지 직접 고르면 된다. 그런데 상태가 10개, 20개로 늘어나고 이벤트도 다양해지면 "이 조합은 테스트했나?"를 손으로 추적하기 어려워진다. 레슨 3의 주문 예시조차 상태 8개에서 이미 40칸이었다. 상태가 15개, 이벤트가 10개면 150칸이다.
모델 기반 테스트(MBT) 는 이 상태 전이 다이어그램을 사람이 읽는 그림이 아니라, 도구가 읽고 순회할 수 있는 모델 로 만든다. 도구가 모델 위의 상태와 전이를 그래프처럼 훑으면서, 그 경로를 실행하는 테스트 케이스를 스스로 만들어낸다. 사람이 하던 "경로 고르기"를 알고리즘에게 넘기는 것이 이 기법의 전부다.
모델은 결국 그래프다 — 점과 화살표
MBT에서 말하는 "모델"이라는 말이 거창하게 들리지만, 실체는 레슨 3의 표와 완전히 같다. 표를 그래프 용어로 바꿔 부르는 것뿐이다.
| 레슨 3의 용어 | 그래프 용어 | 도구가 다루는 방식 |
|---|---|---|
| 상태(State) | 정점(Vertex, Node) | 그래프의 점 |
| 전이(Transition) | 간선(Edge, Arc) | 점과 점을 잇는 화살표 |
| 이벤트(Event) | 간선의 이름 | "이 화살표를 타려면 할 동작" |
| 가드 조건(Guard) | 간선의 조건식 | 조건이 참일 때만 그 화살표를 탈 수 있음 |
| 시작 상태 | 시작 정점 | 모든 경로가 여기서 출발 |
이 대응이 잡히면 그다음이 쉬워진다. "모든 전이를 한 번씩 실행한다"는 말은 그래프 용어로 "모든 간선을 한 번씩 지나는 경로를 찾는다" 가 되고, 이건 컴퓨터가 아주 잘하는 종류의 문제다. 사람이 표를 눈으로 훑으며 빠뜨림을 찾는 것보다, 알고리즘이 간선 목록을 하나씩 지워가는 쪽이 정확할 수밖에 없다.
한 가지 더 중요한 대응이 있다. 정점은 "확인할 것"이고, 간선은 "할
것"이다. 배송중이라는 정점에 도착했다면 거기서 확인해야 할 것은
order.status == 배송중, 송장 번호 존재, 취소 버튼 비노출 같은 것들이고,
송장 등록이라는 간선은 실제로 수행할 동작이다. 이 구분이 나중에 실행
스크립트를 만들 때 그대로 쓰인다.
자동 생성 파이프라인 — 다섯 단계
"자동으로 뽑아낸다"는 말이 마법처럼 들리지 않으려면 중간 단계를 봐야 한다. MBT 도구 안에서 벌어지는 일은 다섯 단계로 나뉜다.
[1] 모델 정의 상태 목록 + 전이 목록 + 시작 상태 + 가드
↓
[2] 커버리지 기준 선택 0-switch? 1-switch? 특정 상태 도달?
↓
[3] 순회 알고리즘 실행 기준을 만족할 때까지 그래프를 걸어다님
↓
[4] 경로 목록 산출 "결제대기 → 결제완료 → 배송준비 → 취소됨" 같은 줄들
↓
[5] 실행 가능한 형태로 변환 각 전이를 API 호출·UI 조작으로, 각 상태를 검증으로사람이 하는 일은 [1]과 [2], 그리고 [5]에서 "각 전이가 실제로 어떤 동작인가"를 한 번 정의해주는 것이다. [3]과 [4]가 자동화되는 부분이다. 많은 사람이 MBT를 "테스트가 전부 저절로 만들어지는 도구"로 오해하는데, 실제로 자동화되는 건 이 두 칸이다. 그리고 이 두 칸이 바로 사람이 가장 많이 실수하는 칸이기도 하다.
손으로 따라가 보기 — 주문 모델 축소판
말로만 하면 감이 안 오니, 레슨 3의 주문 모델에서 반품 부분을 떼어낸 축소판으로 직접 해보자.
[1] 모델 정의. 상태 6개, 전이 7개다.
| # | 현재 상태 | 이벤트(간선 이름) | 다음 상태 | 가드 |
|---|---|---|---|---|
| T1 | 결제대기 | 결제 성공 | 결제완료 | — |
| T2 | 결제대기 | 주문 취소 | 취소됨 | — |
| T3 | 결제완료 | 상품 준비 시작 | 배송준비 | — |
| T4 | 결제완료 | 주문 취소 | 취소됨 | 전액 환불 발생 |
| T5 | 배송준비 | 송장 등록 | 배송중 | — |
| T6 | 배송준비 | 주문 취소 | 취소됨 | 출고 처리 전까지만 |
| T7 | 배송중 | 배송 완료 처리 | 배송완료 | — |
시작 상태는 결제대기, 더 나갈 곳이 없는 상태(취소됨, 배송완료)는 종료 지점이다.
[2] 커버리지 기준 선택 + [3] 순회. 0-switch를 고르면 도구는 "6개 정점을 전부 밟았는가"만 본다. 1-switch를 고르면 "7개 간선을 전부 지났는가"를 본다. 결과가 이렇게 갈린다.
[4] 경로 목록. 0-switch로 순회하면 경로 두 개면 끝난다.
P1: 결제대기 → 결제완료 → 배송준비 → 배송중 → 배송완료 (상태 5개 방문)
P2: 결제대기 → 취소됨 (남은 상태 1개 방문)1-switch로 순회하면 네 개가 나온다.
P1: 결제대기 -T1-> 결제완료 -T3-> 배송준비 -T5-> 배송중 -T7-> 배송완료
P2: 결제대기 -T2-> 취소됨
P3: 결제대기 -T1-> 결제완료 -T4-> 취소됨
P4: 결제대기 -T1-> 결제완료 -T3-> 배송준비 -T6-> 취소됨두 결과를 나란히 놓으면 0-switch의 한계가 그대로 드러난다. P1과 P2만 실행해도 상태 6개는 전부 밟는다. 그런데 T4(결제완료에서 취소)와 T6(배송준비에서 취소)은 한 번도 실행되지 않는다. 그리고 이 둘은 환불이 실제로 발생하는 전이 다. 상태만 다 밟았다고 안심하면 돈이 움직이는 경로 두 개가 통째로 빠진다는 뜻이다. 레슨 3에서 "1-switch가 실무의 기준선"이라고 한 이유가 여기서 숫자로 보인다.
[5] 실행 가능한 형태로 변환. 경로 P3을 실제 테스트로 옮기면 이렇게 된다. 간선은 동작, 정점은 검증이라는 규칙을 그대로 적용한다.
| 순서 | 요소 | 종류 | 실제로 할 일 |
|---|---|---|---|
| 1 | 결제대기 | 정점 → 검증 | 주문 생성 후 status == 결제대기 확인 |
| 2 | T1 결제 성공 | 간선 → 동작 | 결제 승인 API 호출 |
| 3 | 결제완료 | 정점 → 검증 | status == 결제완료, 결제 금액 일치 확인 |
| 4 | T4 주문 취소 | 간선 → 동작 | 취소 API 호출 |
| 5 | 취소됨 | 정점 → 검증 | status == 취소됨, 환불 1건 발생, 재고 복구 확인 |
여기서 눈여겨볼 것이 있다. 사람이 정의한 것은 정점 6개의 검증 방법과 간선 7개의 동작 방법, 합쳐서 13개 뿐이다. 도구는 이 13개를 조합해 경로를 만든다. 상태와 전이가 늘어나도 사람이 정의할 것은 정점 수 + 간선 수만큼만 늘어나는데, 조합될 수 있는 경로 수는 그보다 훨씬 빠르게 늘어난다. MBT의 경제성이 나오는 지점이 정확히 여기다.
순회 알고리즘은 실제로 무엇을 하는가
[3]단계를 조금 더 열어보자. 도구가 쓰는 순회 방식은 대개 두 종류로 나뉜다.
| 방식 | 어떻게 도는가 | 특징 |
|---|---|---|
| 결정적 순회 | 아직 안 밟은 간선을 우선 골라 최단 경로로 찾아감 | 케이스 수가 적고 매번 같은 결과가 나옴 |
| 무작위 순회(random walk) | 갈 수 있는 간선 중 하나를 무작위로 골라 계속 걸음 | 예상 못 한 긴 조합이 나옴. 대신 매번 결과가 달라짐 |
두 방식 모두 정지 조건(stop condition) 이 필요하다. 그래프에 순환이 있으면(예: 반품접수 → 배송완료 → 반품접수) 무한히 걸어다닐 수 있기 때문이다. 그래서 실무에서는 "간선 커버리지 100%에 도달하면 멈춤", "정점 커버리지 80%에 도달하면 멈춤", "300스텝을 걸으면 멈춤" 같은 조건을 함께 지정한다.
무작위 순회는 특히 흥미롭다. 사람은 "결제 → 취소 → 재주문 → 취소 → 재주문"처럼 말이 안 되어 보이지만 실제로는 가능한 긴 조합 을 잘 떠올리지 못한다. 무작위 순회는 그런 조합을 자연스럽게 만들어낸다. 모듈 1 레슨 5에서 본 조합 폭발 문제를, 전부 도는 대신 오래 걸어다니는 것 으로 우회하는 접근이다.
손으로 뽑을 때와 무엇이 결정적으로 다른가
이제 핵심 차이를 정리할 수 있다. 표면적인 차이는 "빠르다"지만, 진짜 차이는 빠뜨림의 성격 에 있다.
| 레슨 3(수동 상태 전이 테스트) | 모델 기반 테스트 | |
|---|---|---|
| 모델을 그리는 사람 | 사람 | 사람 (여기까지는 같다) |
| 경로를 고르는 주체 | 사람이 표를 보고 판단 | 알고리즘이 순회하며 생성 |
| 커버리지 달성 | "대충 다 한 것 같다" | 기준을 기계적으로 100% 만족 |
| 빠뜨림이 생기는 곳 | 표를 보는 눈, 집중력, 시간 | 모델 자체 에만 생김 |
| 요구사항 변경 시 | 케이스를 하나하나 다시 검토 | 모델만 고치면 재생성 |
| 케이스 수 조절 | 사람이 감으로 | 커버리지 기준을 바꿔서 |
가장 중요한 줄은 "빠뜨림이 생기는 곳"이다. 수동으로 하면 빠뜨림이 여러 군데서 생긴다. 표를 잘못 읽어서, 시간이 없어서, 열 번째 행쯤에서 집중력이 떨어져서, 혹은 이번 스프린트에 급해서. 반면 MBT에서는 순회 단계에서는 빠뜨림이 생기지 않는다. 알고리즘은 지치지 않고, 급하다고 건너뛰지 않는다.
대신 빠뜨림이 한 곳으로 모인다 — 모델이다. 이건 좋은 소식이면서 동시에 무서운 소식이다.
모델이 틀리면 그 오류도 자동으로 100% 반영된다
MBT의 약점은 명확하다. 모델 자체가 실제 시스템과 다르면, 아무리 꼼꼼히 순회해도 잘못된 것을 꼼꼼하게 테스트하게 된다. 흔히 Garbage In, Garbage Out(GIGO)이라고 부르는 구조다.
구체적으로 보자. 위 모델에서 실수로 T6(배송준비에서 취소)의 가드 조건을 빼먹고 "언제든 취소 가능"으로 적었다고 하자. 그러면 어떻게 되는가?
- 도구는 T6을 정상 전이로 취급해 경로를 만든다.
- 그 경로가 통과하면 "출고 후 취소도 잘 되네"라는 결론이 나온다.
- 실제로는 레슨 3의 사례 연구에 나온 그 결함 — 환불은 나가고 배송은 진행되는 상황 — 이 그대로 "정상"으로 기록된다.
- 게다가 커버리지 리포트에는 간선 커버리지 100% 라고 찍힌다.
수동 테스트였다면 담당자가 실행하다가 "어? 출고됐는데 취소가 되네?"라고 의심할 여지가 있다. 자동 생성된 케이스는 모델이 기대한 대로 통과했으므로 아무도 멈춰 서지 않는다. 자동화가 오류를 더 빠르고 조용하게 확산시키는 것이다.
이건 모듈 18-A에서 배운 "AI가 생성한 테스트 케이스를 검증 없이 믿으면 안 된다"는 원칙과 근본적으로 같은 구조다. 생성기(모델이든 AI든)가 만든 산출물은 그 입력이 되는 사람의 이해가 정확해야 결과도 정확하다. 그래서 MBT를 쓸 때는 모델 자체를 리뷰 대상으로 올리는 절차 가 반드시 필요하다. 기획자·개발자와 함께 모델을 검토하는 시간이 곧 요구사항 리뷰가 된다 — 레슨 3에서 "표를 그리다 손이 멈추는 칸이 기획자에게 물을 항목"이라고 한 것과 같은 활동이다.
여기에 하나 더. 모델이 커버하는 것은 "정의된 전이"뿐이다. 레슨 3에서 가장 중요하다고 강조한 비정상 전이 — 매트릭스의 빈칸 32개 — 는 모델에 화살표가 없으므로 도구가 알아서 시도해주지 않는다. 빈칸을 테스트하려면 "이 전이는 거부되어야 한다"는 부정 전이(negative edge) 를 모델에 명시적으로 넣어줘야 한다. 이걸 모르면 "MBT를 도입했으니 상태 관련 테스트는 끝났다"는 가장 위험한 착각에 빠진다.
어디에 강한가 — 상태가 많거나, 자주 바뀌거나
MBT의 도입 가치는 두 축으로 판단한다. 상태 복잡도 와 변경 빈도 다.
첫째 축, 상태가 너무 많아 사람이 손으로 나열할 수 없는 시스템.
| 예시 | 왜 사람이 감당 못 하나 |
|---|---|
| 워크플로 엔진(전자결재, 경비 승인) | 결재선이 조직마다 달라 상태·전이가 조합으로 늘어남 |
| 게임 캐릭터 스테이트 머신 | 이동·공격·피격·경직·사망·부활이 서로 끼어들며 전이가 수십 개 |
| 통신 프로토콜 핸드셰이크 | 재전송·타임아웃·중복 패킷까지 넣으면 상태가 폭증 |
| 구독·결제 라이프사이클 | 체험 → 유료 → 연체 → 정지 → 해지 → 복구가 서로 얽힘 |
이런 시스템은 상태-이벤트 매트릭스가 A4 한 장을 넘어가고, 사람이 표를 보며 경로를 고르는 순간 이미 신뢰할 수 없어진다.
둘째 축, 모델이 자주 바뀌는 경우. 오히려 이쪽이 더 실질적인 이유다. 요구사항이 "배송 보류 상태를 하나 추가"처럼 바뀌었다고 하자.
| 수동 | MBT | |
|---|---|---|
| 해야 할 일 | 기존 케이스 중 영향받는 것을 찾아내서 고치고, 새 케이스를 추가 | 모델에 정점 1개와 간선 몇 개를 추가 |
| 위험 | "영향받는 케이스를 다 찾았는가"를 아무도 보장 못 함 | 재생성하면 새 간선이 자동으로 커버에 포함됨 |
| 흔적 | 케이스 문서와 실제 동작이 서서히 어긋남 | 모델이 항상 유일한 정본 |
변경이 잦을수록 이득이 커진다는 점이 중요하다. 한 번 만들고 안 바뀌는 기능이라면 초기 모델링 비용을 회수하지 못한다. 반대로 분기마다 정책이 바뀌는 기능이라면, 모델 하나를 고치는 것으로 수십 개 케이스가 다시 정합해진다.
거꾸로 쓰지 말아야 할 신호 도 분명하다. 상태가 2~3개뿐이거나, 상태 전이보다 값 조합이 본질인 기능(그건 레슨 5의 페어와이즈 영역이다)이거나, 팀에 모델을 유지할 사람이 없는 경우다. 마지막이 실무에서 가장 흔한 실패 원인이다 — 도입한 사람이 퇴사하면 모델이 그날부터 낡기 시작한다.
도구 생태계 — 모델을 어떻게 입력하는가
개념만으로는 "그래서 뭘 어떻게 쓰라는 거지"가 남으니, 대표적인 오픈소스 도구가 모델을 어떤 형태로 받는지만 개념 수준에서 보자.
GraphWalker 가 가장 널리 쓰이는 오픈소스 MBT 도구다. 이름 그대로 "그래프를 걸어다니는(walk)" 도구다. 입력은 그래프 정의 파일 이다. yEd 같은 그래프 편집기로 그린 다이어그램을 GraphML로 저장하거나, JSON으로 직접 쓴다. 파일 안에는 두 종류만 들어간다.
정점(vertex) v_결제대기, v_결제완료, v_배송준비, v_배송중, v_취소됨
간선(edge) e_결제성공, e_주문취소, e_상품준비시작, e_송장등록, ...
(각 간선에 출발 정점, 도착 정점, 가드 조건을 붙인다)그리고 순회 방법을 이런 식으로 지정한다.
random(edge_coverage(100)) 무작위로 걷되, 모든 간선을 밟으면 멈춤
quick_random(vertex_coverage(100)) 빠르게 걷되, 모든 정점을 밟으면 멈춤앞의 random/quick_random이 순회 알고리즘이고, 괄호 안이 정지
조건이다. 우리가 [2]·[3]단계에서 본 것이 그대로 한 줄의 설정으로
표현되는 셈이다.
실행할 때 GraphWalker는 경로를 걸어가면서 정점·간선의 이름과 같은
이름의 메서드를 테스트 코드에서 호출한다. e_송장등록 간선을 지나면
테스트 코드의 e_송장등록() 메서드가 실행되고, v_배송중 정점에 도착하면
v_배송중() 메서드가 실행되어 그 안의 검증이 돌아간다. 즉 QA가 짜는 것은
정점별 검증 코드와 간선별 동작 코드뿐 이고, 순서는 도구가 정한다.
[5]단계의 "사람이 13개만 정의한다"는 말이 실제 코드에서는 이런 모습이다.
이 계열의 다른 도구로는 Java 단위 테스트에 모델을 붙이는 ModelJUnit, GraphWalker를 파이썬에서 쓰게 만든 AltWalker, 마이크로소프트가 만들었던 Spec Explorer(현재는 사실상 유지보수 중단) 등이 있다. 도구 이름을 외울 필요는 없다. 기억할 것은 어느 도구든 입력은 "정점 + 간선 + 커버리지 기준"이고, 출력은 "경로 목록"이라는 구조가 같다 는 점이다.
실무에서 왜 필요한가
사람이 스프레드시트로 추적하던 것을 도구에 넘긴다
상태가 많고 복잡한 시스템(결제, 예약, 승인 워크플로 등)에서는 "이 상태 조합, 테스트했었나?"를 사람이 스프레드시트로 추적하다가 빠뜨리는 일이 흔하다. 특히 릴리스 직전처럼 시간이 없을 때, 사람은 "중요해 보이는 것부터" 고르고 나머지를 조용히 건너뛴다. 모델 기반 테스트는 이 추적을 도구에 맡겨, 모델이 정확한 한 커버리지 기준을 빠짐없이 만족시킨다.
커버리지를 "숫자로 합의"할 수 있게 된다
수동 테스트에서 "상태 전이 테스트 충분히 했나요?"는 답하기 어려운 질문이다. MBT를 쓰면 이 질문이 "간선 커버리지 100%, 정점 커버리지 100%로 돌렸고, 비정상 전이 중 결제 관련 8개를 부정 전이로 모델에 넣었습니다" 같은 구체적인 문장이 된다. 릴리스 판단 회의에서 이 차이는 크다 — 모듈 6에서 배운 "테스트 결과를 근거로 말하기"의 상태 전이 버전이다.
모델링 자체가 요구사항 리뷰가 된다
MBT를 도입한 팀이 가장 먼저 얻는 이득은 사실 자동 생성이 아니라 모델을 만드는 과정 에서 나오는 경우가 많다. 상태와 전이를 도구가 읽을 수 있을 만큼 엄밀하게 적으려면 애매한 표현을 남겨둘 수 없기 때문이다. "출고 전까지만 취소 가능"을 모델에 넣으려면 "출고"의 정확한 시점을 정의해야 하고, 그 순간 기획자에게 질문이 간다. 코드가 한 줄도 없을 때 이 질문을 던질 수 있다는 점도 레슨 3과 같다.
다만 모든 곳에 쓸 도구는 아니다
상태가 단순하거나 자주 안 바뀌는 기능이라면, 레슨 3의 수동 방식이 도구를 도입하는 비용보다 오히려 더 실용적이다. QA는 "이 정도 규모의 상태 복잡도와 변경 빈도라면 모델링 비용을 들일 가치가 있는가"를 판단해야 한다. 도구를 아는 것보다 도구를 쓸지 말지 판단할 수 있는 것 이 실무에서 더 값어치 있는 능력이다.