검증
통과한 테스트가 무엇을 안 보고 있는지. 0이 두 가지를 뜻하면 그 테스트는 고장을 통과시킵니다.
통과한 테스트가 «무엇을 안 보고 있는지»에 대한 글입니다. 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가 측정을 오염시켰다. 내가 주입한 공격이 끝나자마자 보스가 제 패턴으로 다음 공격을 시작해서 카운터 창이 다시 열렸다.
그래서 하네스 기본값을 자율 행동 정지로 바꿨다. 여기까지는 당연한 처방이다.
문제는 그다음이다. 꺼 놓기만 하면, 그 자율 행동이 원래 돌긴 하는지를 아무도 안 본다. 전부 통과하는데 실제 게임에서는 보스가 가만히 서 있는 상태를 놓친다.
그래서 “꺼 둔 그것이 원래 도는가”를 별도 항목으로 남긴다. 격리와 같은 논리인데, 이쪽이 더 자주 잊힌다.
상태가 아니라 결과로 판정한다
전조 타이밍을 잴 때 처음엔 상태 플래그가 언제 바뀌는지를 봤다. 틀렸다.
전조가 끝나도 공격이 안 나갈 수 있다. 사거리나 상태 검사에 걸려서 취소되는 경로가 있다. 플래그만 보면 “전조 끝났음”이 나오는데 플레이어는 안 맞는다.
그래서 판정 기준을 “플레이어가 언제 맞나”로 바꿨다. 내부 상태가 아니라 밖에서 관측되는 결과로 잰다. 같은 이유로 결함 종류마다 판정 방법을 갈라 뒀다 — 화면 결함은 찍어서 보고, 동작 결함은 실제로 돌려서 보고, 구조 결함만 정적으로 본다.
파일만 읽고 “고쳤다”고 보고하지 않는다. 고친 코드를 확인한 것과 고쳐진 게임을 확인한 것은 다르다.
실측했다는 사실이 측정 환경까지 보증하지는 않는다
마지막 하나가 제일 무섭다.
밸런스 상수 하나가 실측 기반이었다. “실제로 재서 넣은 값”이라 아무도 의심하지 않았다.
그런데 판정 경로에 결함이 있었고, 그 결함을 고치자 목표가 클리어 불가능해졌다. 재봤더니 결함이 있던 시절의 측정치가 지표를 3.5배 부풀리고 있었다 — 유령 판정이 킬 수를 올려놓은 것이다.
그래서 규칙을 하나 만들었다. 판정이나 피해 경로의 결함을 고치면, 그 경로에서 파생된 밸런스 상수를 전부 다시 잰다. 그리고 상수 옆에 재는 방법을 주석으로 남긴다.
“실측했다”는 말이 방패가 되면 안 된다. 측정은 측정 당시의 환경까지 같이 측정한다.
왜 이걸 여기서 하나
기법을 남의 프로젝트에서 시험하면 결과를 못 보여준다. 여기서는 보여줄 수 있다.
검증 방법 자체를 검증하는 자리로 쓰고 있다 — 이 하네스가 무엇을 못 보는가, 이 게이트를 통과했는데도 깨지는 경우가 있는가. 위의 “0이 두 가지를 뜻한다”와 “실측이 결함까지 보증하지 않는다”는 둘 다 여기서 나와서 본업 쪽 사고방식으로 넘어갔다.