-
/qa — 함수가 옳아도 화면에 안 나타나는 버그를 잡는 자리 (gstack 시리즈 5/6)IT 2026. 5. 20. 22:00
시리즈 4편의
/review는 코드의 의미를 본다. 그런데 의미가 옳아도 화면 위에서 사라지는 버그가 있다./qa는 그 자리, 곧 사용자가 실제로 보는 픽셀을 검증하는 자리다.왜 이런 스킬이 필요했나
car-game은 6일째 충격적인 상태에 놓였다. 단위 테스트 384개가 모두 통과한 채로, 게임을 띄워 Lv6~10을 플레이하면 도로가 온통 녹색이었다. 풀이 도로를 덮은 게 아니라 도로 자체가 풀색으로 렌더링되고 있었다.
이 상황은 단위 테스트의 본질적 한계를 보여 준다. 단위 테스트는 함수의 약속을 본다 — "이 함수에 X를 주면 Y가 나온다"는 약속이 지켜지는가. 그러나 사용자는 함수의 약속을 보지 않는다. 사용자는 픽셀을 본다. 함수가 약속을 지켜도 그 결과가 다음 함수의 입력에서 깨지면, 함수 단위 테스트로는 잡히지 않는 버그가 화면에 박힌다.
/qa는 그 격차를 메우는 게이트다. 함수의 약속이 아니라 픽셀의 결과를 검증한다.무엇을 하는 스킬인가
/qa는 실제로 게임을 띄우고 사용자처럼 플레이한다. 자동화된 사용자 시뮬레이션 + 화면 캡처 + 차이 검증을 묶어서, "함수가 옳다고 약속한 것"이 아니라 "사용자가 실제로 본 것"을 평가한다.세 가지 단계로 동작한다:
- 실제 환경에서 띄움 — 단위 테스트의 mock 환경이 아니라 실제 브라우저(또는 node-canvas) 위에서 게임을 시작.
- 사용자 동선 모방 — 메뉴부터 게임 오버까지의 흐름을 한 번 통과하면서 각 레벨, 각 상태를 한 번씩 본다.
- 픽셀 단위로 평가 — 화면이 의도와 다른지(검은 캔버스, 빠진 레이어, 잘못된 색)를 보고, 어긋나면 스크린샷을 남긴다.
이 흐름은 단위 테스트와 책임이 다르다. 단위 테스트는 함수의 입출력 약속을,
/qa는 화면의 결과를 본다 — 둘 중 어느 쪽도 다른 쪽을 대체하지 못한다.그림 설명: 위쪽 가로축은 코드가 화면이 되기까지의 흐름이다. 함수 입출력(파랑) → 중간 자료구조(노랑) → 렌더링된 픽셀(분홍). 단위 테스트는 가장 왼쪽의 함수 입출력만 본다. 그러나 사용자는 가장 오른쪽의 픽셀만 본다 — 노란 띠로 표시된 사용자 영역과 직결된 건 분홍 박스, 즉
/qa가 보는 영역이다. 단위 테스트의 영역과 사용자의 영역 사이에 두 단계의 변환이 있고, 그 변환에서 어긋나는 버그는 단위 테스트로 잡히지 않는다.car-game에서 이 스킬이 한 일
/qa는 car-game 6일째에 두 가지 결정적 사고를 단위 테스트보다 먼저 발견했다.사고 1: 도로색이 녹색
도로색은 레벨에 따라 4단계 톤(낮 → 오후 → 황혼 → 밤)을 보간한다. 보간 함수가 hex 문자열에서 채널을 뽑아 RGB를 산술하는 식인데, 한 색 정의가 단축형 hex(
#333같은 3자리)로 들어 있었다. 함수는 풀 폼(#333333) 6자리만 가정하고 작성됐기 때문에, 단축형이 들어왔을 때 채널 추출이 빈 문자열을 반환했고, 산술 결과는 NaN이 됐다.NaN이 섞인 RGB는 브라우저가 칠할 수 없다. 그래서 도로 영역에는 직전에 칠해 둔 잔디색이 그대로 남았고, 도로 전체가 녹색으로 보였다. 단위 테스트는 풀 폼 hex만 입력해서 이 분기를 한 번도 발동시키지 않았다.
/qa의 1회 플레이가 Lv6에서 도로의 색이 잘못된 것을 시각으로 발견했다.사고 2: 레이어 순서
자동차가 물웅덩이 위에 그려져야 하는데 물웅덩이가 자동차 위에 그려지는 일이 있었다. 단위 테스트는 두 객체의 좌표가 정확하면 통과한다 — 그런데 사용자에게는 "차가 물 밑에 깔린" 화면이 보인다.
/qa의 시각 검증이 이 레이어 순서 결함을 감지해, 4-layer 렌더 순서(crosswalk < 지면 장식 < 차/사람 < 플레이어 차)가 도입되는 계기가 됐다.그림 설명: car-game에서
/qa가 단위 테스트보다 먼저 발견한 두 사고를 정리한 표다. 좌측 초록은 도로 녹색 사고 — 함수의 색 산술 자체는 정의대로 동작했지만 NaN이 섞이면서 화면에서는 잔디색이 도로 위에 남았다. 우측 분홍은 레이어 역전 사고 — 좌표 계산은 모두 옳았지만 그리는 순서가 어긋나 사용자에게는 차가 물 밑에 깔린 화면이 보였다. 두 사고의 공통점은 그림 아래 줄에 있다: 함수의 약속은 모두 지켜졌고, 화면의 결과만 어긋났다. 단위 테스트는 약속을 검증하고,/qa는 결과를 검증한다.핵심 메시지
단위 테스트는 함수의 약속을 본다.
/qa는 화면의 결과를 본다. car-game에서/qa는 도로 녹색 + 레이어 순서 두 사고를 단위 테스트보다 먼저 발견해, 사용자가 게임을 켰을 때 보는 픽셀이 의도대로 나오게 만들었다. 테스트 384개가 통과해도/qa를 한 번은 돌려라 — 함수의 약속과 사용자가 보는 픽셀 사이에는 두 단계의 변환이 있고, 그 변환에서 사고가 난다.마지막 글에서는 한 단계 더 들어가 본다.
/qa나 단위 테스트가 통과한 것 자체가 의미가 있는지를 의심하는 자리,/investigate다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
t2v, i2v, flf2v, omni_reference — Seedance 2.0 fast 약어가 가리키는 것과 조합 매트릭스 (0) 2026.05.22 Seedance 2.0 fast가 실존 인물 사진을 거절했다 — 우회 말고 Real Human Asset 등록이 정도 (0) 2026.05.21 Seedance 2.0 fast는 한글 손글씨를 보존할 수 있다 — diffusion 정설을 뒤집은 prompt 4축 (0) 2026.05.21 200 OK가 거짓말을 한다 — Seedance 2.0 fast API의 silent fallback을 잡아내는 법 (0) 2026.05.21 /investigate — Trivial pass의 뿌리를 캐는 자리 (gstack 시리즈 6/6) (0) 2026.05.20 /review — 테스트가 못 보는 의미 부패를 청소하는 자리 (gstack 시리즈 4/6) (0) 2026.05.20 /plan-* 3종 — 한 PLAN 문서를 세 시선으로 통과시키기 (gstack 시리즈 3/6) (1) 2026.05.19 /office-hours — 막연한 컨셉을 plan으로 갈 수 있게 깎는 자리 (gstack 시리즈 2/6) (0) 2026.05.19 Ralph-loop만으로는 부족한 이유, 그리고 gstack의 자리 — gstack 시리즈 (1/6) (0) 2026.05.19 한 285줄 스크립트가 self-recovering 자율 시스템이 되기까지 — ralph-loop.sh 의 8 commit 진화사 (Ralph Loop 시리즈 8편 통합) (0) 2026.05.18