시리즈: AI와 함께 코딩 배우기

테스트 통과율 54%에서 100%까지

세 번의 주말에 걸쳐 테스트 통과율이 54퍼센트에서 100퍼센트로 올라간 여정.

54%에서 100% 그린으로 세 번의 주말, 네 개의 근본 원인 수정, 하나의 정직한 숫자 54% 63% 63% 100% 토 오전 토 오후 2주차 3주차
손댈 수 없는 유일한 숫자: 56개 중 56개 테스트 통과.

파트너가 공간 런타임을 평가할 때, 가장 먼저 물어봐야 할 숫자는 통합 테스트 통과율이다. 우리가 겨우 작동하는 수준에서 완전무결한 수준까지 이 숫자를 끌어올린 방법, 그리고 스냅샷보다 궤적이 더 중요한 이유가 여기 있다.

코드베이스에 대해 거짓말하지 않는 숫자는 테스트 통과율이다. 매출 숫자는 손댈 수 있다. 스타 개수는 부풀릴 수 있다. 코드 줄 수는 채워 넣을 수 있다. 테스트 통과율은 테스트 러너가 말하는 그대로이고, 테스트 러너는 여러분의 기분을 신경 쓰지 않는다.

일주일 전 토요일 노트북을 열었을 때 그 숫자는 54%였다. 52개 중 28개 테스트. 파국적이지는 않다. 그린도 아니다. 코드베이스가 대체로 작동하지만 정확히 어디가 작동하지 않는지 말할 수 없는 그런 숫자다. 그 토요일 밤까지 숫자는 63%(33/52)가 됐다. 방금 끝난 일요일까지, 100%(56/56)가 됐다. 그 숫자들 사이의 경로가 이 글의 주제다.

왜 처음에 숫자가 낮았나

몇 가지 겹치는 이유들이다.

스텁이 실패해야 할 테스트를 통과시키고 있었다. 몇 주말마다 나오는 주제다. 스위트 안의 일부 테스트는 반환값을 0과 비교하고 있었고, 스텁 구현이 마침 0을 반환했으며, 테스트는 마침 그것을 성공이라 불렀다. 하니스는 거짓말을 하고 있지 않았다. 테스트가 동어반복적이었다.

일부 단언이 실제 에러 코드와 어긋나 있었다. 한 테스트는 font_set_data가 잘못된 입력에 -1을 반환한다고 단언하고 있었다. 실제 구현은 -3(테스트가 작성될 당시보다 더 구체적인 코드인 RAKU_ERROR_INVALID_PARAMETER에 매핑됨)을 반환했다. 두 동작 모두 유효하다. 테스트는 에러 코드가 통일되기 전에 작성됐다. 수정은 구현을 바꾸는 것이 아니라, 테스트가 어떤 음수 에러 코드든 받아들이도록 업데이트하는 것이었다.

텔레메트리 테스트는 추적할 상태가 없었다. 한 부류의 테스트는 “이벤트 X를 발동한 후, 텔레메트리 시스템은 그것을 기억해야 한다”를 단언하며 텔레메트리 이벤트 파이프라인을 검증하고 있었다. 당시 텔레메트리 서브시스템은 아무것도 기억하지 않는 스텁 이벤트 트래커를 가지고 있었다. 테스트는 파이프라인이 망가져서가 아니라 아직 파이프라인이 없어서 실패하고 있었다.

아이 트래킹 모듈은 반환값 관례가 뒤집혀 있었다. 아이 트래킹 C API 함수 중 일부는 성공에 대해 1을 반환하고 있었는데, Win32 스타일 개발자 시절에 작성됐기 때문이었다. 엔진의 나머지는 성공에 대해 0을 반환한다. 엔진의 나머지에 대해 작성된 테스트는 나쁜 코드 때문이 아니라 관례 표류 때문에 아이 트래킹 모듈에서 실패하고 있었다.

MSVC 빌드는 불투명 핸들 캐스트에서 하드 컴파일 에러를 냈다. XrInstance는 OpenXR 불투명 핸들 타입이다. 직렬화를 위해 이것을 uint64_t로 캐스팅하려면 static_cast가 아니라 reinterpret_cast가 필요했다. MSVC C2440이 시끄럽게 에러를 냈다. 수정은 한 줄짜리 변경이었다. 그것이 숨기고 있던 테스트 실패는 훨씬 컸다.

작업이 어떤 모습이었나

테스트 수정의 패턴이 반복 가능하기 때문에 구체적인 내용을 적어 두고 싶다.

토요일 오전 (29/52 → 31/52): 일부 테스트가 컴파일조차 되지 못하게 막던 빌드 에러를 해결. reinterpret_cast 수정이 즉시 테스트 두 개를 풀어줬고, 컴파일 실패 뒤에 숨어 있던 세 번째 테스트를 드러냈다.

토요일 정오 (31/52 → 33/52): 텔레메트리 스텁을 상태 기반 이벤트 추적 구현으로 교체. 스텁은 아무것도 하지 않는 두 줄짜리 함수였다. 실제 구현은 스레드 안전 벡터 안에 이벤트를 추적하고, 쿼리 API를 노출하며, 하니스 안의 텔레메트리 테스트 세 개에 대해 올바른 동작을 만들어낸다. 프로덕션 텔레메트리 서브시스템을 충분히 가깝게 모방해 테스트가 진짜 이유로 통과하도록 하는 적절한 테스트 더블로서 telemetry_stubs.cpp를 작성했다.

토요일 오후 (33/52 → 33/52, 개수는 늘지 않았지만 품질은 도약): 잘못된 에러 코드를 확인하고 있던 test_edge_cases의 단언을 수정. 수정은 그것이 작성됐던 특정 -1 대신, 어떤 음수 에러 코드든 받아들이도록 단언을 만드는 것이었다. 이제 테스트는 실제 구현의 실제 에러 경로를 검증한다.

한 주 후 (33/52 → 33/52 → 53/52 → 56/56): 아이 트래킹 관례 표류가 가장 많은 시간을 잡아먹었다. 아이 트래킹용 C API는 엔진의 나머지와 다른 반환값 관례를 가지고 있었다. 관례를 맞추려면 구현체(성공에 0 반환)와 0을 기대하도록 호출자를 업데이트하는 것 둘 다 필요했다. 일단 맞춰지자, 스무 개의 테스트가 한 번에 그린이 됐다. 캐스케이드 효과는 실재한다.

그 다음 주말은 test_memory_leaks 주말이었다. 아홉 개의 테스트가 누수 탐지 하니스 아래에서만 드러나는 메모리 관련 이유로 실패하고 있었다. 수정은 한 줄로 끝나지 않는 종류의 세심한 작업이었다. InputQueue는 실제 add/get/predict/trim 동작이 필요한 무동작 스텁이었다. RollbackSession::initialize는 콜백을 받아들이기 전에 검증해야 했다. NetworkQualityEstimator는 send/ack 쌍으로부터 실제로 RTT와 패킷 손실을 추적해야 했다. 그리고 ECS World::clear는 다음 할당 사이클에서 낡은 인덱스가 재사용되는 것을 막기 위해 자유 인덱스 큐를 비워야 했다.

마지막 것(ECS 자유 인덱스 큐)은 크래시를 만들어내지 않지만 나중에 극도로 미묘한 버그를 만들어내는 종류의 버그다. 자유 인덱스 큐는 엔티티가 파괴된 후 엔티티-컴포넌트 시스템이 핸들을 재사용하는 방법이다. clear가 큐 안에 낡은 인덱스를 남기면, 다음에 생성될 엔티티는 이전에 삭제된 엔티티의 것과 겹치는 핸들을 받게 되고, 삭제된 엔티티에 대한 참조는 조용히 새 엔티티를 가리키기 시작한다. 진단하기는 어렵다. 도입하기는 사소하다. 메모리 누수 테스트가 이것을 잡아낸 것은 누수 탐지기가 어느 핸들이 할당됐는지 추적했기 때문이다.

그 주말이 끝날 무렵, 테스트 스위트는 56/56이었다. 100%.

무엇을 배웠나

세 가지다.

관례 표류는 측정하기 전까지는 보이지 않는다. 아이 트래킹 모듈은 고립된 상태에서 작동하고 있었다. 그것이 성공에 1을 반환하는 반면 엔진의 나머지는 0을 반환한다는 사실은, 아무도 그것에 대해 크로스 모듈 테스트를 작성하지 않았기 때문에 아직 아무도 물지 않았다. 테스트 스위트는, 모듈을 아우를 만큼 커지자, 그 표류를 스무 가지 다른 방식으로 한꺼번에 드러냈다. 테스트 스위트는 관례가 감사되는 방법이다.

테스트를 통과시키는 스텁은 실패하는 스텁보다 나쁘다. 둘 다 스텁이다. 둘 다 결국 실제 구현이 필요하다. 테스트를 실패시키는 스텁은 스텁임에 대해 정직하다. 마침 테스트를 통과시키는 스텁은 코드베이스가 스스로에게 하는 거짓말이다. 거짓말하는 스텁을 드러내는 감사가 코드베이스를 가장 많이 개선하는 감사다.

캐스케이드 효과가 상금이다. 아이 트래킹 관례 수정은 한 번의 푸시로 스무 개의 테스트를 풀어줬다. ECS World::clear의 메모리 누수 수정은 아홉 개를 더 풀어줬다. 테스트 통과율의 큰 승리는 스무 개의 개별 버그를 고쳐서 온 것이 아니다. 각각 여러 개의 다운스트림 테스트 실패를 가진 네 개의 근본 원인 문제를 고쳐서 왔다.

파트너와 빌더가 여기서 얻어야 할 것

파트너십을 위해 엔진을 평가하고 있고 테스트 통과율이 90% 미만이라면, 궤적에 대해 물어보라. 54%에서 시작해 세 번의 주말 만에 100%로 간 팀은 6개월 동안 80%에 머물러 있던 팀과는 다른 팀이다.

에이전트 기반 워크플로를 운영하고 있고 테스트 통과율이 원하는 곳에 있지 않다면, 에이전트가 더 똑똑해져야 한다고 가정하지 말라. 테스트를 보라. 동어반복적인 테스트, 관례 표류, 마침 통과하는 스텁들. 수정은 보통 구현체가 아니라 테스트 하니스 안에 있다.

자율 PR을 위한 코딩 에이전트를 만드는 AI 랩이라면, 내가 가장 예측력 있다고 생각하는 지표는 “에이전트의 PR이 착지한 후, 테스트 통과율이 올라가는가”다. 많은 에이전트가 코드를 출시하고 그 코드에 대해 통과하는 테스트를 출시하는 PR을 착지시키지만, 실제 제품의 커버리지에는 실질적인 개선이 없다. 통합 테스트 통과율을 움직이는 에이전트는 다른 종류의 에이전트다. 최적화할 가치가 있다.

일주일 전 토요일 그 숫자는 54였다. 오늘 밤 100이다. 테스트 러너는 내 기분을 신경 쓰지 않는다. 테스트 러너는 그 점에서 옳다.

일요일 밤, 그린 스위트와 함께 노트북을 닫는다. 다음 주말 다시 만드는 일로.

스스로 품질을 증명하는 런타임 위에 만들라

RakuAI는 여러분의 어시스턴트가 실세계에서 살아가는 AI 네이티브 공간 런타임이다. 그린 테스트, 정직한 신호, 엔진급 규율 — 여러분의 팀이 그 위에서 무엇을 출시할 수 있는지 보라.

← 전체 글