개념
테스트는 입력이 아니라 판정이다
모듈 3에서 배운 설계 기법은 전부 입력 공간을 어떻게 나눌 것인가 에 대한 답이다. 동등 분할은 입력을 묶고, 경계값은 묶음의 가장자리를 고르고, 페어와이즈는 조합을 줄인다. 그런데 입력을 아무리 잘 골라도 실행 결과를 보고 맞다/틀리다를 판정할 수 없으면 그건 테스트가 아니라 그냥 실행 이다.
이 판정의 근거를 테스트 오라클(test oracle) 이라고 부른다. 오라클은 "이 입력에 대해 무엇이 올바른 결과인가"를 알려주는 원천이다. 케이스 문서의 기대 결과 칸은 오라클의 출력이지 오라클 자체가 아니다.
입력 선택 → 실행 → 판정
(설계 기법) (오라클)
모듈 3 이 레슨오라클 문제
소프트웨어 공학에서 오라클 문제(oracle problem) 는 오래된 난제다. 많은 경우 "정답"을 값싸게 구할 방법이 없다.
- 추천 알고리즘이 사용자에게 보여준 상품 10개가 "올바른" 결과인가
- 이미지 압축 결과가 "충분히" 원본과 같은가
- 과학 계산 코드가 낸 부동소수점 값이 맞는가 — 맞는 값을 계산할 수 있으면 애초에 이 코드가 필요 없다
- 대규모 마이그레이션 후 3억 건의 레코드가 전부 제대로 옮겨졌는가
실무 QA가 오라클 문제를 만나는 지점은 대개 더 소박하다. 명세가 그 경우를 말하지 않는다. 그래서 기대 결과 칸에 "정상 동작", "오류 없이 처리된다" 같은 문장이 들어간다. 이건 오라클이 없다는 뜻이고, 그 케이스는 어떤 결과가 나와도 통과시킬 수 있다.
오라클의 다섯 가지 출처
오라클은 다섯 갈래에서 온다. 실무에서는 한 케이스에 여러 개를 겹쳐 쓴다.
| 출처 | 무엇인가 | 신뢰도 | 비용 | 대표 적용 |
|---|---|---|---|---|
| ① 명세 오라클 | 요구사항·설계서·API 계약·표준 문서 | 높음(명세가 맞다면) | 낮음 | 기능 케이스 대부분 |
| ② 회귀 오라클 | 이전 버전의 동작, 저장된 골든 결과 | 중간 | 낮음 | 리팩터링, 마이그레이션 |
| ③ 병행 구현 오라클 | 같은 일을 하는 다른 구현·경쟁 제품·스프레드시트 | 중간~높음 | 중간 | 계산 로직, 전환 검증 |
| ④ 불변식 오라클 | 입출력이 항상 만족해야 하는 성질 | 높음(부분적) | 중간 | 원장·재고·상태 기계 |
| ⑤ 휴리스틱 오라클 | 일관성·상식·사용자 기대에 근거한 판단 | 낮음(하지만 넓음) | 낮음 | 탐색적 테스팅, UI |
① 명세 오라클 은 기본이다. 문제는 명세가 모든 경우를 덮지 않는다는 것. 모듈 2 레슨 3에서 다룬 모호함·누락 찾기가 여기 직접 연결된다. 명세 공백을 발견했을 때 할 일은 기대 결과를 추측해 적는 것이 아니라, 공백을 질문으로 만들어 되돌려 보내는 것 이다. 이게 리뷰 활동이 되고 레슨 2로 이어진다.
② 회귀 오라클 은 "예전에는 이랬다"를 정답으로 쓴다. 값싸고 강력하지만 함정이 하나 있다. 예전 동작이 결함이었다면 그 결함을 정답으로 고정한다. 골든 파일·스냅샷 테스트가 이 함정에 빠지기 쉽다. 스냅샷을 갱신할 때 diff를 사람이 읽지 않으면 오라클이 조용히 오염된다.
③ 병행 구현 오라클 은 같은 계산을 하는 다른 경로와 비교한다. 흔히 의사 오라클(pseudo-oracle) 이라고 부른다. 금융 차세대 전환에서 현행 시스템과 신규 시스템의 계산 결과를 대조하는 것(모듈 41 레슨 3), 배치 집계 결과를 SQL 한 줄로 다시 세어 보는 것이 전부 여기 해당한다. 두 구현이 같은 오해를 공유하면 둘 다 틀릴 수 있다는 한계가 있다.
④ 불변식 오라클 은 "정답 값"이 아니라 "정답이 만족해야 하는 성질" 을 검사한다. 정답을 몰라도 쓸 수 있다는 것이 결정적인 장점이다.
값을 모르지만 이건 항상 참이어야 한다:
- 차변 합계 == 대변 합계 (모듈 41 레슨 2)
- 정렬 후 길이 == 정렬 전 길이
- 장바구니 총액 == 품목 소계의 합 - 할인 + 배송비
- 상태는 정의된 전이 그래프의 간선으로만 이동한다
- 두 번 호출한 결제 승인 요청이 만든 승인 건수 <= 1⑤ 휴리스틱 오라클 은 "명세에 없지만 이건 이상하다"를 판정한다. 탐색적 테스팅의 핵심 도구이고, 모듈 5 레슨 14의 휴리스틱 치트시트가 이 오라클의 목록이다. 판정이 흔들릴 수 있으므로 결함 보고서에 근거를 함께 적는 습관 이 필요하다 — "비슷한 다른 화면은 이렇게 동작한다", "같은 제품 안에서 이 버튼만 다르게 동작한다" 같은 형태다.
일관성 휴리스틱 — 무엇과 비교해 이상한가
휴리스틱 오라클을 막연한 "감"에서 진술 가능한 판정으로 끌어올리는 방법은, 무엇과의 불일치인지를 지목하는 것 이다. 비교 대상은 대략 여덟 갈래다.
| 비교 대상 | 판정 문장의 형태 |
|---|---|
| 제품 내부 | 같은 제품의 다른 화면과 동작이 다르다 |
| 과거 버전 | 이전 릴리스에서는 이렇게 동작했다 |
| 비교 제품 | 같은 범주의 다른 제품은 이렇게 한다 |
| 주장 | 사용자 매뉴얼·마케팅 문구가 이렇게 말한다 |
| 목적 | 이 기능이 존재하는 이유와 어긋난다 |
| 사용자 기대 | 사용자가 합리적으로 기대할 동작이 아니다 |
| 표준·규제 | 표준·법령이 정한 방식과 다르다 |
| 자기 자신 | 같은 입력에 두 번 다른 결과가 나온다 |
마지막 항목이 실무에서 가장 강하다. 같은 입력에 다른 출력 은 명세가 없어도 거의 항상 결함이거나 최소한 설명이 필요한 현상이다.
오라클이 없을 때 — 부분 오라클과 메타모픽 관계
정답을 전혀 모를 때도 쓸 수 있는 두 가지 장치가 있다.
부분 오라클(partial oracle) 은 정답의 일부만 판정한다. 추천 결과가 "올바른지"는 판정 못 해도 이런 것들은 판정할 수 있다.
- 응답에 항상 10개가 온다
- 품절 상품이 섞여 있지 않다
- 방금 구매한 상품이 다시 추천되지 않는다
- 같은 상품이 중복되지 않는다
- 성인 카테고리가 미성년 계정에 나오지 않는다메타모픽 테스팅(metamorphic testing) 은 정답 대신 입력을 변형했을 때 출력이 어떻게 변해야 하는가 라는 관계를 검사한다. 관계를 메타모픽 관계(MR) 라고 부른다.
검색 기능
MR1: 검색어에 조건을 하나 더 붙이면 결과 수가 줄거나 같아야 한다
MR2: 정렬 옵션만 바꾸면 결과 집합(순서 무시)은 동일해야 한다
MR3: 대소문자만 바꾸면 결과가 같아야 한다
운임 계산
MR4: 무게가 커지면 운임이 줄지 않아야 한다 (단조성)
MR5: 주문을 둘로 쪼개 배송하면 합계 운임이 한 번에 보낼 때보다 작지 않다메타모픽 관계는 AI·검색·추천처럼 정답을 특정할 수 없는 기능에서 특히 유용하다. 모듈 38 레슨 1의 "검증 가능한 기준"도 같은 계열의 문제의식이다.
속성 기반 테스트로 불변식 오라클 자동화하기
불변식을 코드로 옮기면 무작위 입력을 대량으로 던져 위반을 찾을 수 있다.
from hypothesis import given, strategies as st
# 대상: 장바구니 금액 계산
@given(
items=st.lists(
st.tuples(st.integers(min_value=1, max_value=999_999), # 단가
st.integers(min_value=1, max_value=99)), # 수량
min_size=1, max_size=20),
coupon_rate=st.integers(min_value=0, max_value=100),
)
def test_cart_invariants(items, coupon_rate):
cart = calculate_cart(items, coupon_rate)
# ① 합계 불변식 — 정답 값을 몰라도 검사 가능
assert cart.subtotal == sum(price * qty for price, qty in items)
# ② 할인은 소계를 넘지 않는다
assert 0 <= cart.discount <= cart.subtotal
# ③ 결제 금액은 음수가 될 수 없다
assert cart.payable >= 0
# ④ 원 단위 정합 — 부동소수점이 새어 들어오지 않았는가
assert isinstance(cart.payable, int)
# ⑤ 구성 요소의 합이 총액과 일치한다
assert cart.payable == cart.subtotal - cart.discount + cart.shipping_fee③번과 ④번은 실제 사고에서 반복적으로 나오는 유형이다. 쿠폰 중복 적용으로 결제 금액이 음수가 되는 결함, 할인율 계산에 부동소수점이 섞여 1원이 어긋나는 결함은 예제 기반 케이스로는 잘 안 잡히고 무작위 입력에서 잘 잡힌다.
케이스 템플릿에 오라클 출처 칸을 넣는다
이 레슨의 실무 산출물은 하나다. 테스트 케이스 템플릿에 "기대 결과 근거" 칸을 추가하는 것.
| TC | 입력 | 기대 결과 | 오라클 출처 |
|---|---|---|---|
| TC-101 | 1,000원 상품 + 10% 쿠폰 | 결제 금액 900원 | 명세 PRD-4.2 §할인 계산 |
| TC-102 | 쿠폰 2장 동시 적용 | 오류 "쿠폰은 1장만" | 근거 없음 — 확인 필요 |
| TC-103 | 무작위 주문 500건 | 불변식 ①~⑤ 위반 0건 | 불변식 (속성 기반) |
| TC-104 | v2.3 동일 시나리오 200건 | v2.3 결과와 동일 | 회귀(골든 파일 2026-08-01) |
TC-102가 이 칸을 넣는 이유다. 칸이 없으면 담당자는 그럴듯한 기대 결과를 적고 넘어간다. 칸이 있으면 비어 있는 것이 눈에 보이고, 그게 명세 질문이 된다. 실제로 이 칸을 도입한 팀에서 가장 먼저 생기는 변화는 케이스 품질이 아니라 기획자에게 가는 질문의 수다.
오라클의 실패 모드 세 가지
오라클도 틀린다. 세 가지 실패 모드를 미리 알아두면 설계에서 막을 수 있다.
| 실패 모드 | 증상 | 예방 |
|---|---|---|
| 틀린 오라클 | 명세가 틀렸거나, 골든 파일이 결함을 담고 있다 | 골든 갱신 시 diff 리뷰 의무화, 명세 근거를 케이스에 남겨 추적 가능하게 |
| 침묵하는 오라클 | 판정 코드가 있는데 실제로는 아무것도 검사하지 않는다 | 변이 테스트·의도적 결함 주입으로 "이 오라클이 실패를 잡는가" 확인 |
| 흔들리는 오라클 | 타임스탬프·정렬 순서·랜덤 시드 때문에 같은 코드가 다른 판정을 받는다 | 비결정 요소 정규화(시간 고정, 순서 무시 비교, 시드 고정) |
침묵하는 오라클은 자동화에서 특히 위험하다. 모듈 43 레슨 2의 자동화 코드 리뷰 체크리스트 AT-1(검증 존재)이 이 문제를 막는 첫 방어선이고, 두 번째 방어선은 일부러 결함을 심어 테스트가 빨개지는지 확인하는 것 이다.
오라클 강도와 비용의 절충
모든 케이스에 최강의 오라클을 붙일 수는 없다. 리스크로 결정한다.
오라클 강도
▲
강 │ 병행 구현 + 불변식 ← 결제·원장·정산 (모듈 41)
│ 명세 + 불변식 ← 핵심 업무 규칙
│ 명세 ← 일반 기능
약 │ 휴리스틱 ← 보조 화면, 탐색적 세션
└──────────────────────▶ 비용리스크 등급(모듈 4 레슨 2)과 오라클 강도를 같은 표에 놓으면 판단이 쉬워진다. 최고 리스크 기능에 휴리스틱 오라클만 붙어 있다면 그건 분석이 덜 끝난 상태다.
실무에서 왜 필요한가
기대 결과가 약한 케이스는 통과하는 테스트 를 만든다. 통과하는 테스트는 아무도 문제 삼지 않는다. 그래서 오라클 부실은 커버리지 숫자로도, 결함 수로도 드러나지 않고 운영 장애로만 드러난다. 장애 회고에서 "테스트는 다 통과했는데요"가 나오는 상황의 상당수가 오라클 문제다 — 케이스는 그 시나리오를 실행했지만, 잘못된 결과를 통과로 판정했던 것이다.
오라클 출처를 명시하는 습관은 또 다른 효과를 낸다. 명세의 공백이 테스트 설계 단계에서 목록으로 드러난다. 개발이 끝난 뒤 "이 경우는 어떻게 되나요"를 묻는 것과, 설계 착수 전에 12건을 한 번에 묻는 것은 비용이 완전히 다르다. 이것이 분석이 기법보다 앞에 있어야 하는 이유다.
채용시장에서의 위치
면접에서 "기대 결과를 어떻게 정하셨나요"는 겉보기에 쉬운 질문이지만 답이 확연히 갈린다. "기획서를 보고 적었습니다"에서 끝나는 답과, "명세가 덮지 않는 구간은 불변식으로 판정하고, 계산 로직은 스프레드시트 병행 구현과 대조했습니다" 라는 답 사이의 거리가 이 레슨의 내용이다. 특히 금융·정산·데이터 플랫폼 포지션에서는 오라클 설계 경험이 직접적인 채용 근거가 된다.