개념
비결정적(Non-deterministic)이란
같은 입력을 줘도 실행할 때마다 다른 결과가 나올 수 있는 코드를 비결정적이라 한다. 대표적인 원인 세 가지가 있다.
- 현재 시각 —
new Date()같은 값은 실행할 때마다 다르다 - 랜덤 값 — 난수 생성기는 매번 다른 값을 만든다
- 외부 API 응답 — 네트워크 상태, 외부 서비스 상태에 따라 응답이 달라지거나 실패할 수 있다
모듈 7(레슨 3)에서 배운 플래키 테스트(어떤 때는 통과, 어떤 때는 실패하는 테스트)의 상당수가 이 비결정적 요소를 통제하지 못해서 생긴다.
시간 통제하기
"12월에만 무료배송 이벤트가 적용된다"는 로직을 테스트한다고 하자.
function isFreeShippingEvent() {
return new Date().getMonth() === 11 // 12월(0부터 시작하므로 11)
}이 함수는 테스트를 실행하는 실제 날짜에 따라 결과가 달라진다 — 12월에 테스트를 돌리면 통과하지만, 다른 달에 돌리면 실패한다. 해결 방법은 현재 시각을 코드 안에 직접 박아두지 않고, 바깥에서 주입받게 만드는 것이다(모듈 9 레슨 9의 테스트 용이성과 정확히 같은 원리).
function isFreeShippingEvent(currentDate) {
return currentDate.getMonth() === 11
}
// 테스트에서는 원하는 날짜를 직접 넣어서 검증
test("12월이면 무료배송 이벤트가 적용된다", () => {
const december = new Date(2026, 11, 15)
expect(isFreeShippingEvent(december)).toBe(true)
})많은 테스트 프레임워크는 아예 시스템 시각 자체를 고정시키는
기능(예: jest.useFakeTimers())도 제공한다.
랜덤 값 통제하기
랜덤 값이 필요한 코드(예: 쿠폰 코드 생성)를 테스트할 때는, 난수 생성기에 고정된 시드(seed) 를 줘서 항상 같은 "랜덤" 값이 나오게 만든다. 시드가 같으면 몇 번을 실행해도 같은 순서의 값이 나온다 — 겉보기엔 랜덤이지만 실은 예측 가능해지는 것이다.
외부 API 통제하기
레슨 3에서 배운 테스트 더블(스텁)을 써서, 실제 외부 서비스 대신 항상 같은 응답을 돌려주는 가짜로 바꿔 끼운다. 이렇게 하면 외부 서비스가 실제로 느려지거나 응답이 바뀌어도 우리 테스트 결과는 영향을 받지 않는다.
비결정성을 완전히 없앨 수 없는 경우
E2E(End-to-End) 테스트나 실제 운영 환경 점검처럼, 진짜 시간과 진짜 외부 서비스로 검증해야 하는 순간도 있다. 이럴 땐 비결정성을 없애기보다, 여유 있는 대기 시간, 재시도 로직으로 불안정성을 줄이는 접근을 쓴다 — 이는 스테이지 4(자동화)에서 더 자세히 다룬다.
실무에서 왜 필요한가
"어제는 통과했는데 오늘은 실패한다"는 리포트를 받았을 때, 그 원인이 진짜 코드 결함인지, 아니면 시간·랜덤·외부 API 같은 비결정적 요소 때문인지 구분할 수 있는 QA는 훨씬 빠르게 원인을 좁힐 수 있다. 이건 모듈 8(레슨 5)에서 배운 "인시던트가 곧 결함은 아니다"라는 원칙을 실제 코드 수준에서 적용하는 것이다.