Navigation

2. 시나리오와 기대값

GIVEN, WHEN, THEN으로 조건, 행동, 결과를 드러내고 상태와 경계값을 선택합니다. 기대값은 검증 대상 구현과 독립된 계약에서 정합니다.

2.1 조건과 관찰 가능한 결과로 테스트 하나를 설명합니다

실패한 제목만으로 깨진 동작과 필요한 사전조건을 찾을 수 있습니다.

제목은 조건과 관찰 가능한 결과를 설명하고 테스트 하나는 그 동작에 필요한 준비, 실행, 검증을 담습니다. 하나의 표시 계약에 속한 여러 필드나 부수효과는 같은 테스트에서 함께 단언할 수 있습니다.

시나리오 표기

실행 사례의 기본 표기는 GIVEN, WHEN, THEN 블록 주석입니다. 라벨 다음 줄에 그 사례의 조건, 행동, 결과를 한국어 명사구로 적습니다. 짧은 unit, integration, E2E, 골든 예제에도 같은 표기를 보여 줍니다.

구간담는 것
GIVEN입력, 응답 덮어쓰기, 세션 등 사전조건
WHEN검증할 함수 호출, mount, 사용자 조작
THEN반환값, DOM, URL 등 관찰 가능한 결과의 단언

리터럴 호출에 별도 준비가 없으면 WHEN, THEN만 남길 수 있습니다. 이미 그려진 상태만 확인하면 GIVEN, THEN만 남길 수 있습니다. 짧다는 이유로 실제 존재하는 구간을 숨기거나 빈 구간을 채우려고 불필요한 코드를 만들지 않습니다. fixture와 test.each가 준비한 조건도 출처를 설명하고 주석을 위해 별칭을 만들지 않습니다. fixture, handler, story 선언과 러너 설정 자체에는 실행 사례처럼 세 구간을 붙이지 않습니다.

여러 단계와 결과서

독립적인 실패 원인이면 테스트를 나누거나 입력별로 이름이 남는 매개변수화를 사용합니다. 같은 여정의 여러 바퀴는 시나리오 문장이 있는 test.step으로 나눕니다. trace에 단계가 필요하면 step에도 GIVEN, WHEN, THEN과 의미를 적고 주석과 중복하지 않습니다. 주석과 step을 결과서로 읽는 프로젝트에서는 파서가 지원하는 형식을 유지합니다. GWT 주석을 위해 Cucumber나 별도 BDD 러너를 추가하지 않습니다.

참고: Gherkin의 조건, 행동, 결과.

이름만으로 어떤 조건이 깨졌는지 알 수 없습니다.

src/util/number/format-quantity.test.tsts
import { expect, test } from "vitest";

import { formatQuantity } from "@/util/number/format-quantity";

test("잘된다", () => {
	/**
	 * GIVEN
	 * 빈 수량과 일반 수량
	 */
	const input = { empty: null, quantity: 1234 };

	/**
	 * WHEN
	 * 서로 다른 두 표시 계약의 실행
	 */
	const labels = { empty: formatQuantity(input.empty), quantity: formatQuantity(input.quantity) };

	/**
	 * THEN
	 * 서로 다른 결과를 모은 단언
	 */
	expect(labels.empty).toBe("-");
	expect(labels.quantity).toBe("1,234.00");
});

제목과 본문이 같은 표시 동작을 설명합니다.

src/util/number/format-quantity.test.tsts
import { expect, test } from "vitest";

import { formatQuantity } from "@/util/number/format-quantity";

test("수량이 없으면 빈 값 표시를 반환한다", () => {
	/**
	 * GIVEN
	 * 없는 수량
	 */
	const quantity = null;

	/**
	 * WHEN
	 * 수량의 표시 문자열 계산
	 */
	const label = formatQuantity(quantity);

	/**
	 * THEN
	 * 빈 값 표시
	 */
	expect(label).toBe("-");
});

test("수량이 있으면 천 단위 구분과 소수 두 자리로 표시한다", () => {
	/**
	 * GIVEN
	 * 천 단위 구분이 필요한 수량
	 */
	const quantity = 1234;

	/**
	 * WHEN
	 * 수량의 표시 문자열 계산
	 */
	const label = formatQuantity(quantity);

	/**
	 * THEN
	 * 천 단위 구분과 소수 두 자리
	 */
	expect(label).toBe("1,234.00");
});

2.2 계약에 해당하는 상태와 경계값을 선택합니다

정상 경로만 통과하는 테스트에서 빠지기 쉬운 회귀를 검출합니다.

변경한 계약에 해당하는 상태와 경계값을 고릅니다. 가능한 모든 값을 늘어놓거나 타입이 보장하는 내부 입력에 임의의 잘못된 값을 넣지 않습니다.

계약검토할 사례
범위, 반올림, 날짜하한과 상한의 안팎, 0, 음수, 월말처럼 판정이 바뀌는 값
목록, 선택정상, 0건, 선택 없음, 마지막 항목 처리
비동기 화면대기, 성공, 오류, 계약에 있는 재시도와 부분 실패
입력, 상호작용유효성, 비활성화, 키보드와 포커스 등 직접 구현한 동작

0건인 성공 응답과 요청 실패는 별개 상태로 봅니다. 오류 코드와 경계 조합은 실제 처리 분기가 있는 것을 선택하며 모든 상태 코드의 일괄 테스트를 요구하지 않습니다.

참고: 공식 문서.

양수 정상 입력만 확인합니다.

src/util/number/format-quantity.test.tsts
import { expect, test } from "vitest";

import { formatQuantity } from "@/util/number/format-quantity";

test("양수 수량을 표시한다", () => {
	/**
	 * GIVEN
	 * 일반 양수 하나
	 */
	const value = 1234;

	/**
	 * WHEN
	 * 수량의 표시 문자열 계산
	 */
	const label = formatQuantity(value);

	/**
	 * THEN
	 * 양수의 표시 형식
	 */
	expect(label).toBe("1,234.00");
});

0과 빈 값의 서로 다른 계약을 이름별로 확인합니다.

src/util/number/format-quantity.test.tsts
import { expect, test } from "vitest";

import { formatQuantity } from "@/util/number/format-quantity";

test.each([
	{ name: "양수", value: 1234, expected: "1,234.00" },
	{ name: "0", value: 0, expected: "0.00" },
	{ name: "빈 값", value: null, expected: "-" },
])("$name 수량의 표시 계약을 지킨다", (input) => {
	/**
	 * GIVEN
	 * test.each의 이름으로 구분되는 경계 입력
	 */

	/**
	 * WHEN
	 * 수량의 표시 문자열 계산
	 */
	const label = formatQuantity(input.value);

	/**
	 * THEN
	 * 입력 표에 적힌 독립 기대값
	 */
	expect(label).toBe(input.expected);
});

2.3 기대값은 구현과 독립된 계약에서 정합니다

구현과 같은 오류를 기대값에서도 반복해 통과하는 테스트를 막습니다.

기대값은 요구사항, 직접 확인할 수 있는 작은 예제, 명시적인 불변 관계에서 정합니다. 검증 대상 함수를 다시 호출하거나 같은 알고리즘과 제품 상수를 그대로 복사해 기대값을 만들지 않습니다.

구체적인 값이 계약이면 그 값과 구조를 확인합니다. 존재나 참 여부가 계약일 때만 존재 단언을 사용합니다. 관계가 계약이면 정렬 순서, 합계 보존처럼 그 관계와 실패 입력을 분명히 드러냅니다. 현재 구현을 정답으로 받아 기준값을 갱신하기 전에 변경 의도를 확인합니다.

참고: 공식 문서.

검증 대상 함수를 기대값에서도 호출합니다.

src/util/number/format-quantity.test.tsts
import { expect, test } from "vitest";

import { formatQuantity } from "@/util/number/format-quantity";

test("수량을 표시한다", () => {
	/**
	 * GIVEN
	 * 천 단위 구분이 필요한 수량
	 */
	const quantity = 1234;

	/**
	 * WHEN
	 * 수량의 표시 문자열 계산
	 */
	const label = formatQuantity(quantity);

	/**
	 * THEN
	 * 같은 구현으로 다시 계산한 기대값
	 */
	expect(label).toBe(formatQuantity(quantity));
});

직접 확인한 출력값과 비교합니다.

src/util/number/format-quantity.test.tsts
import { expect, test } from "vitest";

import { formatQuantity } from "@/util/number/format-quantity";

test("수량을 표시한다", () => {
	/**
	 * GIVEN
	 * 천 단위 구분이 필요한 수량
	 */
	const quantity = 1234;

	/**
	 * WHEN
	 * 수량의 표시 문자열 계산
	 */
	const label = formatQuantity(quantity);

	/**
	 * THEN
	 * 직접 확인한 표시 형식
	 */
	expect(label).toBe("1,234.00");
});