ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • /investigate — Trivial pass의 뿌리를 캐는 자리 (gstack 시리즈 6/6)
    IT 2026. 5. 20. 23:00
    /investigate — Trivial pass의 뿌리를 캐는 자리 (gstack 시리즈 6/6)

    시리즈 5편의 /qa는 화면 위의 버그를 잡았다. 마지막 글은 한 단계 더 들어가 본다 — 테스트가 분명히 통과하는데, 그 통과가 의미 있는 통과인지 의심하는 자리, /investigate가 car-game에서 어떤 기여를 했는지.

    왜 이런 스킬이 필요했나

    테스트가 통과하면 안심하고 넘어가고 싶다. 그런데 통과의 종류가 두 가지다.

    • 의미 있는 통과 (meaningful pass): 테스트가 의도한 조건이 실제로 실행됐고, 그 결과가 약속과 일치한다.
    • 사소한 통과 (trivial pass): 테스트가 의도한 조건이 실행되지도 않은 채로 단언이 통과한다. 두 빈 캔버스를 비교하면서 "다르지 않다"고 통과하는 식.

    가장 위험한 건 trivial pass다. 통과는 통과인데, 검증은 일어나지 않았다. 이런 테스트는 있다는 사실 자체가 거짓 안전감을 만든다 — 사용자는 "테스트가 잡아 줄 것"으로 믿고 코드를 늘리는데, 실제로는 아무도 보고 있지 않다.

    /investigate는 trivial pass를 의심하는 자리다. "왜 이게 통과했지?"를 묻고, 첫 가설에 만족하지 않고 증상이 사라질 때까지 다음 가설을 묻는다. 테스트 인프라조차 검증 대상으로 삼는 메타 시선이 거기서 나온다.

    무엇을 하는 스킬인가

    /investigate의 핵심은 3-strike rule이다. "이 한 가지 가설이 맞으면 모든 게 설명된다"는 유혹에 빠지지 않고, 가설마다 다음 질문을 던진다 — "이 가설만으로 증상이 다 사라지는가?" 사라지지 않으면 다음 가설을 세운다.

    세 가지 단계로 작동한다:

    1. 증상 정의: 무엇이 어긋났는가를 한 문장으로 명료하게 적는다.
    2. 가설 분기: 증상을 만들 수 있는 원인 후보를 모두 나열한다.
    3. 반복 검증: 가설 하나를 가정하고 고친 뒤, 증상이 완전히 사라지는지 확인한다. 사라지지 않으면 다음 가설로 넘어간다 — 첫 가설로는 만족하지 않는다.

    diagram

    그림 설명: 위쪽 주황 박스가 의심받는 증상이다. /investigate는 첫 가설(파란 박스)을 세우고 고친 뒤 두 가지 갈래로 분기한다 — 증상이 모두 사라지면(초록) 종료, 아직 남으면(빨강) 다음 가설로 내려간다. 가설 N에 도달할 때까지 같은 분기가 반복된다. 핵심은 초록 갈래에서만 종료한다는 점이다. "한 가설로 절반이 설명되니 됐다"는 멈춤은 trivial pass를 다시 만든다.

    car-game에서 이 스킬이 한 일

    car-game에는 시각 결정성(determinism) 테스트가 있다. "같은 seed → 같은 PNG, 다른 seed → 다른 PNG"라는 단언이다. 이 테스트가 처음부터 통과하고 있었다. 좋은 신호로 보였다.

    그런데 한 번 두 PNG를 직접 열어 봤다. 두 PNG가 모두 빈 검은 캔버스였다. "다른 seed에서 다른 PNG가 나왔다"가 아니라 "둘 다 똑같이 비어 있어서 다르지 않다는 단언이 자동으로 통과"한 것이었다 — 단언은 실행됐지만 검증은 일어나지 않았다. 가장 교과서적인 trivial pass였다.

    /investigate가 3-strike rule로 가설을 캐냈다. 첫 가설(seed가 게임 인스턴스에 전달되지 않음)에서 멈추지 않고, 각자 단독으로도 빈 캔버스를 만들 수 있는 4개의 결함을 한 번에 끄집어냈다:

    1. Game 생성 시 rng를 안 넘김 — 화면 흔들림 RNG가 seed의 영향을 받지 않는다.
    2. gameover 분기에서 _shakeTimer=0 — RNG 호출이 0회. 어떤 seed든 결과가 같다.
    3. clear 분기에서 _particles 빈 배열 — confetti 입자가 0개. 그릴 게 없다.
    4. frames=0이면 렌더 루프가 한 번도 안 돈다 — 캔버스가 그대로 검정.

    네 결함은 모두 동시에 깨져 있어서 테스트가 trivial pass로 통과하고 있었다. 첫 결함 한 개만 고치면 어떻게 됐을까? rng는 들어가지만 gameover 분기에선 rng를 안 쓰니 PNG는 여전히 비어 있다. 두 번째까지 고쳐도 clear 분기는 또 비어 있다. 각 단계의 수리는 다음 결함 앞에서 무력화된다.

    diagram

    그림 설명: 좌상의 주황 박스가 증상이다. /investigate가 첫 가설(결함 #1)을 발견한 뒤 멈추지 않고 같은 증상을 만들 수 있는 결함 #2, #3, #4를 줄줄이 캐낸다. 네 결함은 모두 각자 단독으로도 빈 캔버스를 만들 수 있다 — 그래서 한 개만 고치면 다음 결함이 즉시 다시 빈 캔버스를 만들고, 테스트는 또 trivial pass로 통과한다. 가운데 초록 박스가 결론이다: 네 결함을 한 번에 수리해야 결정성 테스트가 의미 있는 검증으로 바뀐다. 그림 아래의 빨간 줄이 보여 주는 시나리오는 "한 가설에서 멈췄을 때"의 결과 — 안전하다는 거짓 신호가 다시 만들어진다.

    핵심 메시지

    테스트가 통과한다고 의미가 통과한 게 아니다. Trivial pass도 pass다 — 그리고 trivial한 통과는 가장 위험하다. car-game에서 /investigate는 결정성 테스트가 두 빈 캔버스를 비교하며 통과하던 trivial pass의 4단 결함을 한 번에 캐냈다. 첫 가설에서 멈췄다면 안전하다는 거짓 신호가 누적됐을 것이다. 한 가설에 만족하지 않고 증상이 사라질 때까지 다음 가설을 묻는 — 그게 /investigate가 보여 주는 사고법이고, 테스트 인프라조차 검증 대상이라는 메타 시선이 거기서 나온다.

    시리즈 마무리 — 6편을 묶는 한 줄

    ralph-loop은 거리를 늘린다. gstack 스킬은 거리를 잘라 의미를 만든다.

    기획에선 컨셉을 깎고, 세 시선으로 PLAN을 다듬고, 구현에선 의미 부패를 청소하고, 화면 위의 결과를 확인하고, 운영에선 trivial한 통과를 의심하는(이 글) — 다섯 게이트가 ralph-loop의 거리에 의미를 박는다. 어느 한 게이트라도 빠졌다면 car-game은 여섯째 날에 도로가 녹색인 채로, 또는 결정성이 trivial pass인 채로, 또는 PLAN이 한 머리의 사각지대를 그대로 안고 — 다른 형태의 부채를 안고 굴러갔을 것이다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.