본문으로 건너뛰기
고급약 40분

테스트 오라클 설계 — 기대 결과를 어디서 가져올 것인가

테스트 케이스의 절반은 입력이고 나머지 절반은 기대 결과다. 그런데 기대 결과의 출처를 적어 두는 팀은 드물다. 명세·이전 버전·병행 구현·불변식·휴리스틱이라는 다섯 가지 오라클 출처를 신뢰도와 비용으로 비교하고, 오라클이 없는 영역을 부분 오라클과 메타모픽 관계로 덮으며, 오라클 자체가 틀리거나 침묵하는 실패 모드를 설계 단계에서 막는 법을 익힌다.

  • #테스트오라클
  • #기대결과
  • #메타모픽테스팅
  • #불변식
  • #휴리스틱

개념

테스트는 입력이 아니라 판정이다

모듈 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-1011,000원 상품 + 10% 쿠폰결제 금액 900원명세 PRD-4.2 §할인 계산
TC-102쿠폰 2장 동시 적용오류 "쿠폰은 1장만"근거 없음 — 확인 필요
TC-103무작위 주문 500건불변식 ①~⑤ 위반 0건불변식 (속성 기반)
TC-104v2.3 동일 시나리오 200건v2.3 결과와 동일회귀(골든 파일 2026-08-01)

TC-102가 이 칸을 넣는 이유다. 칸이 없으면 담당자는 그럴듯한 기대 결과를 적고 넘어간다. 칸이 있으면 비어 있는 것이 눈에 보이고, 그게 명세 질문이 된다. 실제로 이 칸을 도입한 팀에서 가장 먼저 생기는 변화는 케이스 품질이 아니라 기획자에게 가는 질문의 수다.

오라클의 실패 모드 세 가지

오라클도 틀린다. 세 가지 실패 모드를 미리 알아두면 설계에서 막을 수 있다.

실패 모드증상예방
틀린 오라클명세가 틀렸거나, 골든 파일이 결함을 담고 있다골든 갱신 시 diff 리뷰 의무화, 명세 근거를 케이스에 남겨 추적 가능하게
침묵하는 오라클판정 코드가 있는데 실제로는 아무것도 검사하지 않는다변이 테스트·의도적 결함 주입으로 "이 오라클이 실패를 잡는가" 확인
흔들리는 오라클타임스탬프·정렬 순서·랜덤 시드 때문에 같은 코드가 다른 판정을 받는다비결정 요소 정규화(시간 고정, 순서 무시 비교, 시드 고정)

침묵하는 오라클은 자동화에서 특히 위험하다. 모듈 43 레슨 2의 자동화 코드 리뷰 체크리스트 AT-1(검증 존재)이 이 문제를 막는 첫 방어선이고, 두 번째 방어선은 일부러 결함을 심어 테스트가 빨개지는지 확인하는 것 이다.

오라클 강도와 비용의 절충

모든 케이스에 최강의 오라클을 붙일 수는 없다. 리스크로 결정한다.

오라클 강도
   ▲
강 │  병행 구현 + 불변식      ← 결제·원장·정산 (모듈 41)
   │  명세 + 불변식           ← 핵심 업무 규칙
   │  명세                    ← 일반 기능
약 │  휴리스틱                ← 보조 화면, 탐색적 세션
   └──────────────────────▶ 비용

리스크 등급(모듈 4 레슨 2)과 오라클 강도를 같은 표에 놓으면 판단이 쉬워진다. 최고 리스크 기능에 휴리스틱 오라클만 붙어 있다면 그건 분석이 덜 끝난 상태다.

실무에서 왜 필요한가

기대 결과가 약한 케이스는 통과하는 테스트 를 만든다. 통과하는 테스트는 아무도 문제 삼지 않는다. 그래서 오라클 부실은 커버리지 숫자로도, 결함 수로도 드러나지 않고 운영 장애로만 드러난다. 장애 회고에서 "테스트는 다 통과했는데요"가 나오는 상황의 상당수가 오라클 문제다 — 케이스는 그 시나리오를 실행했지만, 잘못된 결과를 통과로 판정했던 것이다.

오라클 출처를 명시하는 습관은 또 다른 효과를 낸다. 명세의 공백이 테스트 설계 단계에서 목록으로 드러난다. 개발이 끝난 뒤 "이 경우는 어떻게 되나요"를 묻는 것과, 설계 착수 전에 12건을 한 번에 묻는 것은 비용이 완전히 다르다. 이것이 분석이 기법보다 앞에 있어야 하는 이유다.

채용시장에서의 위치

면접에서 "기대 결과를 어떻게 정하셨나요"는 겉보기에 쉬운 질문이지만 답이 확연히 갈린다. "기획서를 보고 적었습니다"에서 끝나는 답과, "명세가 덮지 않는 구간은 불변식으로 판정하고, 계산 로직은 스프레드시트 병행 구현과 대조했습니다" 라는 답 사이의 거리가 이 레슨의 내용이다. 특히 금융·정산·데이터 플랫폼 포지션에서는 오라클 설계 경험이 직접적인 채용 근거가 된다.

실습 과제

과제 1 — 오라클 출처 감사 (20분)

담당 중인 기능의 테스트 케이스 20건을 뽑아 각 케이스의 기대 결과가 어느 출처에서 왔는지 표에 채운다.

TC기대 결과오라클 출처근거 문서·위치강도(강/중/약)

채운 뒤 두 숫자를 센다. 출처가 "없음"인 비율 과 리스크 상위 기능인데 오라클 강도가 "약"인 케이스 수 다. 두 번째 숫자가 이 과제의 진짜 발견이다.

과제 2 — 불변식 다섯 개 뽑아 코드로 옮기기 (25분)

담당 기능에서 "값은 몰라도 이건 항상 참이어야 한다"를 다섯 개 적는다. 금액·개수·상태·시간·권한 각 축에서 하나씩 뽑으면 고르게 나온다.

그중 셋을 속성 기반 테스트로 옮긴다(Python은 Hypothesis, JS/TS는 fast-check). 작성 후 반드시 불변식을 깨는 코드를 일부러 넣어 테스트가 실패하는지 확인한다 — 침묵하는 오라클을 거르는 단계다.

과제 3 — 메타모픽 관계 만들기 (15분)

정답을 특정하기 어려운 기능을 하나 고른다(검색, 추천, 요금 계산, 정렬, 리포트 집계 중 하나). 메타모픽 관계 세 개를 아래 형식으로 적고, 각각에 대해 한 번씩 수동으로 확인한다.

MR-1  입력을 이렇게 바꾸면 → 출력은 이래야 한다 → 실제 결과: 만족/위반

위반이 나오면 그 자리에서 결함 후보다. 위반이 없으면 "관계가 너무 느슨한 건 아닌가"를 검토한다.

자가 체크리스트

  • 테스트 오라클과 기대 결과 칸의 차이를 설명할 수 있다
  • 오라클의 다섯 가지 출처를 들고, 각각의 신뢰도와 한계를 말할 수 있다
  • 회귀 오라클이 결함을 정답으로 고정하는 상황과 그 예방책을 설명할 수 있다
  • 정답을 모르는 기능에 부분 오라클과 메타모픽 관계를 적용할 수 있다
  • 불변식을 속성 기반 테스트로 옮기고 실패하는지 확인할 수 있다
  • 휴리스틱 판정을 여덟 가지 비교 대상 중 하나로 지목해 진술할 수 있다
  • 오라클의 세 가지 실패 모드(틀림·침묵·흔들림)를 식별하고 예방책을 적용할 수 있다
  • 리스크 등급과 오라클 강도를 같은 표에서 대조해 보강 지점을 고를 수 있다

흔한 실수

  • 기대 결과에 "정상 동작", "오류 없이 처리"라고 적는다. 오라클이 없다는 뜻이고, 그 케이스는 어떤 결과도 통과시킨다. 관측 가능한 값(상태·금액·레코드·로그)으로 바꾼다.
  • 명세 공백을 만나면 기대 결과를 추측해 채운다. 추측이 문서에 남으면 나중에 근거로 재인용된다. 비워 두고 질문으로 올린다.
  • 골든 파일·스냅샷을 diff 없이 갱신한다. 결함이 정답으로 승격된다. 갱신은 항상 diff 리뷰를 동반해야 한다.
  • 자동화 테스트에 검증이 없는 것을 모른 채 둔다. 통과율 100%가 검증 0건일 수 있다. 의도적 결함 주입으로 한 번씩 확인한다.
  • 정답을 모르니 테스트를 못 한다고 결론 낸다. 부분 오라클과 메타모픽 관계로 상당 부분을 덮을 수 있다. "전부 판정"과 "전혀 판정 못 함" 사이가 넓다.
  • 비결정 요소를 오라클에 그대로 넣는다. 타임스탬프·정렬 순서·랜덤 값 때문에 흔들리는 테스트는 결국 무시되고, 무시되는 테스트는 없는 테스트다.

참고 자료

낯선 말 풀이

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

QA — 품질보증
만든 것이 쓸 만한지 확인하고, 애초에 덜 망가지게 만드는 방법을 정하는 일. "테스트"는 그 일의 일부다.
API — 응용 프로그램 인터페이스
프로그램끼리 주고받는 창구. 사람이 화면 버튼을 누르는 대신, 프로그램이 정해진 주소로 요청을 보내 답을 받는다.
UI — 사용자 인터페이스
사람이 직접 보고 누르는 부분 — 화면, 버튼, 입력창.
SQL — 구조화 질의 언어
저장소에 "이런 조건인 줄을 보여 달라"고 묻는 말. 영어 문장에 가까워서 읽기가 비교적 쉽다.
AI — 인공지능
사람이 규칙을 일일이 적지 않아도 데이터에서 규칙을 찾아 판단하는 소프트웨어.
TC — 테스트 케이스
무엇을 어떻게 해보고 무엇이 나와야 하는지를 한 건으로 적어 둔 것. 시험 문제 한 문항에 해당한다.
ISTQB — 국제 소프트웨어 테스팅 자격 위원회
테스터 자격 시험을 만들고 용어를 정리하는 국제 단체.
CTAL-TA — 상급 자격 · 테스트 분석
상급 테스터 자격 중 테스트 설계·분석을 다루는 과목.

용어집 전체 보기