# 검증

> 통과한 테스트가 무엇을 안 보고 있는지. 0이 두 가지를 뜻하면 그 테스트는 고장을 통과시킵니다.

- 수치: 배선 게이트
- 상태: wip
- 사람이 보는 화면: https://aitryed.com/ko/built/moon-studio/wuxia/qa/
- 반대 언어: https://aitryed.com/en/built/moon-studio/wuxia/qa/index.md
- 이 사이트 안내(AI용): https://aitryed.com/ko/llms.txt

---

**통과한 테스트가 «무엇을 안 보고 있는지»에 대한 글입니다. 0이 두 가지를 뜻하면, 그 테스트는 고장을 통과시킵니다.**

## 하나 — 「검사 통과」는 「동작함」이 아닙니다

제일 먼저 배운 것입니다.

프로그램이 **오류 없이 통과**하는 것과, **켜서 제대로 도는 것**은 다릅니다.

| | 무엇을 보증하나 |
|---|---|
| 오류 없이 통과 | **문법이 맞음** |
| 켜서 돌려 봄 | **의도대로 동작함** |

그런데 **오류가 안 나면 다 된 것 같은 기분**이 듭니다. 초록불이 뜨니까요.

**아무 일도 안 하는 코드는 오류가 절대 안 납니다.**

## 둘 — 「0이 두 가지를 뜻하면 그 테스트는 고장을 통과시킵니다」

이 글에서 제일 값나가는 한 줄입니다.

결과가 **0**으로 나왔습니다. 두 가지일 수 있습니다.

| 실제 | 화면 |
|---|---|
| **검사했고 문제 없음** | **0** |
| **검사 자체가 안 돎** | **0** |

**둘이 똑같이 보이고, 둘 다 좋은 소식처럼 보입니다.**

그래서 검사기가 고장 나면 **「전부 통과」로 보입니다.** 이게 최악입니다 — **고장이 합격으로 읽힙니다.**

갈라야 합니다.

- **「0건 (검사 12개 실행)」** ← 검사했고 없음
- **「실행 안 됨」** ← 0이 아님

## 셋 — 「격리한 이유를 주석이 아니라 «테스트»가 말하게 합니다」

어떤 테스트를 잠시 꺼 둘 때가 있습니다. 그리고 보통 **주석으로 이유를 적습니다.**

> `// 이건 아직 구현 안 돼서 꺼 둠`

문제는 — **주석은 아무도 안 읽고, 구현이 끝나도 안 켭니다.** 그리고 몇 달 뒤에 **왜 꺼져 있는지 아무도 모릅니다.**

그래서 **끄는 대신 「실패하는 것이 정상」으로 표시**합니다. 그러면 **구현이 끝나서 통과하기 시작하면 오히려 알림이 뜹니다.**

> **끄면 조용해지고, 「실패가 정상」으로 두면 고쳐졌을 때 알려 줍니다.**

## 넷 — 「끈 것이 «원래는 도는지»도 검사합니다」

위와 짝이 되는 장치입니다.

무언가를 꺼 두면, **그게 꺼져서 안 도는 건지 원래 고장인지** 구분이 안 됩니다.

그래서 **켠 상태로 한 번 돌려 보는 검사**를 따로 둡니다. 켜면 돌아야 합니다. 안 돌면 **끈 것과 상관없는 고장**입니다.

## 다섯 — 「상태가 아니라 «결과»로 판정합니다」

앞의 구현 글에서 나온 것과 같은 이야기입니다.

「이 값이 이렇게 되어 있으면 성공」으로 판정하면, **값은 맞는데 실제로는 안 된 경우**를 놓칩니다.

**결과로 판정합니다** — 무언가가 실제로 만들어졌는지, 실제로 바뀌었는지.

## 여섯 — 「실측했다는 사실이 «측정 환경»까지 보증하지는 않습니다」

제일 뼈아팠던 것입니다.

숫자를 **실제로 쟀습니다.** 그러니 믿을 만하다고 생각했습니다.

그런데 **재는 동안 게임이 정상 상태가 아니었습니다.** 판정 쪽에 결함이 있어서 **유령 판정이 섞여** 있었고, 그게 숫자를 **3.5배 부풀렸습니다.**

그리고 그 결함을 고치니 **게임을 클리어할 수 없게** 됐습니다 — 그 유령 판정이 없으면 원래 못 깨는 난이도였던 것입니다.

즉 **잰 것은 맞는데, 무엇을 재고 있었는지가 틀렸습니다.**

> **「실측했다」가 「맞다」를 보증하지 않습니다. 재는 동안 환경이 정상이었나도 같이 봐야 합니다.**

그리고 배운 처방이 이겁니다 — **판정이나 피해 계산을 고쳤으면, 거기서 파생된 상수를 전부 다시 재야 합니다.**

## 왜 이걸 게임에서 하나

마지막으로 적어 둡니다.

이런 검증 이야기는 **제 본업 쪽 이야기**이기도 합니다. 그런데 본업 쪽은 **자세히 못 씁니다.**

이 게임은 제 것이라 **어디서 어떻게 틀렸는지 전부 적을 수 있습니다.**

**공개적으로 부술 수 있는 물건이 있어야, 부순 이야기를 쓸 수 있습니다.**

## 여기부터는 자세한 기록입니다

내가 온전히 소유한 유일한 QA 대상이다. 그래서 여기서는 **테스트가 놓치는 것**을 마음껏 실험할 수 있고, 실제로 여기서 나온 게 본업 쪽으로 넘어간다.

## 하네스 PASS는 작동 증명이 아니다

편집기 모드에서 도는 테스트 하네스는 빠르고 편하다. 그리고 **런타임 배선 구멍을 통째로 가린다.**

함수는 잘 돈다. 클래스도 맞다. 그런데 씬에서 그 참조가 비어 있으면 게임에서는 아무 일도 안 일어난다. 하네스는 그걸 못 본다 — **자기가 객체를 만들어서 테스트하기 때문**이다.

실제로 이렇게 샌 적이 있다. 가드 코드가 `참조가 있고 그게 활성이면 통과시킨다`였는데, 씬에서 그 참조가 연결돼 있지 않았다. 그러면 조건이 항상 거짓이라 **가드가 매번 스킵된다.** 컴파일도 통과하고, 코드 리뷰도 통과하고, 런타임 예외도 안 난다. 조용히 안 도는 것뿐이다.

**코드 리뷰는 `.cs`만 본다.** 그래서 씬에 저장된 참조를 직접 읽는 게이트를 따로 뒀다. 코드가 맞느냐가 아니라 **배선이 되어 있느냐**를 묻는다. 특히 같은 참조를 한 컴포넌트엔 연결하고 다른 컴포넌트엔 빠뜨리는 **부분 누락**이 제일 잘 샌다.

## 0이 두 가지를 뜻하면 그 테스트는 고장을 통과시킨다

이 프로젝트에서 얻은 것 중 제일 값어치 있는 규칙이다.

파훼가 제대로 도는지 재려고 "막았을 때 피해가 0인가"를 테스트로 걸었다. 통과했다.

그런데 **"막아서 피해 0"과 "사거리 밖이라 피해 0"이 구별되지 않는다.** 방어 기능이 완전히 죽어 있어도, 어쩌다 사거리를 벗어나 있으면 그 테스트는 초록불이다.

드러난 경위가 재미있다. 오답 계열로 받았을 때는 피해가 60%여야 하는데, 그 케이스가 **`피해 0.0 (기대 13.2)`으로 실패**했다. 오답인데 0이 나왔다는 건 애초에 아무도 안 맞고 있었다는 뜻이고, 그 순간 **정답 케이스의 통과가 거짓이었다는 게 같이 드러났다.**

그래서 순서를 바꿨다. **방어를 재기 전에, 막지 않았을 때 실제로 맞는지를 먼저 세운다.** 그 항목이 통과해야 방어 항목의 0이 의미를 갖는다.

## 격리한 이유를 주석이 아니라 테스트가 말하게 한다

무언가를 떼고 재는 일이 잦다. "방어막을 떼고 순수 피해를 잰다" 같은 것.

그럴 때 **방어막이 원래 무엇을 하는지 검사하는 항목을 같이 둔다.** 그 동작이 어느 날 사라지면 그 항목이 먼저 실패해서, **격리의 전제가 무너졌다는 걸 알려준다.**

주석으로 "여기선 방어막을 뗐음"이라고 적어 두면 아무 일도 일어나지 않는다. 전제가 조용히 바뀌어도 주석은 그대로 남아 있기 때문이다.

## 끈 것이 원래 도는지도 검사한다

보스 패턴을 재려고 하네스를 세웠는데 **자율 AI가 측정을 오염시켰다.** 내가 주입한 공격이 끝나자마자 보스가 제 패턴으로 다음 공격을 시작해서 카운터 창이 다시 열렸다.

그래서 하네스 기본값을 **자율 행동 정지**로 바꿨다. 여기까지는 당연한 처방이다.

문제는 그다음이다. 꺼 놓기만 하면, 그 자율 행동이 **원래 돌긴 하는지**를 아무도 안 본다. 전부 통과하는데 실제 게임에서는 보스가 가만히 서 있는 상태를 놓친다.

그래서 **"꺼 둔 그것이 원래 도는가"를 별도 항목으로 남긴다.** 격리와 같은 논리인데, 이쪽이 더 자주 잊힌다.

## 상태가 아니라 결과로 판정한다

전조 타이밍을 잴 때 처음엔 상태 플래그가 언제 바뀌는지를 봤다. 틀렸다.

**전조가 끝나도 공격이 안 나갈 수 있다.** 사거리나 상태 검사에 걸려서 취소되는 경로가 있다. 플래그만 보면 "전조 끝났음"이 나오는데 플레이어는 안 맞는다.

그래서 판정 기준을 <strong>"플레이어가 언제 맞나"</strong>로 바꿨다. 내부 상태가 아니라 밖에서 관측되는 결과로 잰다. 같은 이유로 결함 종류마다 판정 방법을 갈라 뒀다 — 화면 결함은 찍어서 보고, 동작 결함은 실제로 돌려서 보고, 구조 결함만 정적으로 본다.

**파일만 읽고 "고쳤다"고 보고하지 않는다.** 고친 코드를 확인한 것과 고쳐진 게임을 확인한 것은 다르다.

## 실측했다는 사실이 측정 환경까지 보증하지는 않는다

마지막 하나가 제일 무섭다.

밸런스 상수 하나가 실측 기반이었다. "실제로 재서 넣은 값"이라 아무도 의심하지 않았다.

그런데 판정 경로에 결함이 있었고, **그 결함을 고치자 목표가 클리어 불가능해졌다.** 재봤더니 결함이 있던 시절의 측정치가 지표를 **3.5배 부풀리고 있었다** — 유령 판정이 킬 수를 올려놓은 것이다.

그래서 규칙을 하나 만들었다. **판정이나 피해 경로의 결함을 고치면, 그 경로에서 파생된 밸런스 상수를 전부 다시 잰다.** 그리고 상수 옆에 재는 방법을 주석으로 남긴다.

"실측했다"는 말이 방패가 되면 안 된다. **측정은 측정 당시의 환경까지 같이 측정한다.**

## 왜 이걸 여기서 하나

기법을 남의 프로젝트에서 시험하면 결과를 못 보여준다. 여기서는 보여줄 수 있다.

**검증 방법 자체를 검증하는 자리**로 쓰고 있다 — 이 하네스가 무엇을 못 보는가, 이 게이트를 통과했는데도 깨지는 경우가 있는가. 위의 "0이 두 가지를 뜻한다"와 "실측이 결함까지 보증하지 않는다"는 둘 다 여기서 나와서 본업 쪽 사고방식으로 넘어갔다.
