TECH NOTE · AUTOMATION OPS
자동화가 안 되는 확인은 남습니다. 체크리스트를 문서가 아니라 실행되는 스크립트로 만들고 커밋 해시를 보고서에 박는 방법입니다.
눈으로 봐야 하는 항목이 남는다
데스크톱 앱을 배포하기 전에 확인할 것 중에는 자동화가 안 되는 게 있다.
- 투명 창이 실제로 투명하게 보이는가
- 캐릭터가 화면 밖으로 안 나가는가
- 마이크 권한 요청 문구가 제대로 뜨는가
- 앱을 종료하면 정말 프로세스가 사라지는가
E2E 도구를 붙이면 일부는 된다. 하지만 “보기에 이상하지 않은가”는 결국 사람이 본다.
문제는 사람이 매번 다르게 본다는 것이다. 지난번엔 확인했는데 이번엔 잊고, 어제는 세 개를 봤는데 오늘은 다섯 개를 본다. 그러고는 “확인했다”고 똑같이 말한다.
체크리스트를 문서로 두면 안 지켜진다
처음에는 마크다운 문서로 체크리스트를 만들었다.
docs/101-desktop-manual-qa-checklist.md
잘 안 지켜졌다. 이유가 몇 가지 있다.
열어 볼 이유가 없다. 배포 직전에 급하면 기억나는 것만 확인하고 넘어간다. 문서는 열지 않으면 존재하지 않는 것과 같다.
결과를 남길 자리가 없다. 항목을 봤는지 안 봤는지가 아무 데도 안 남는다. 다음 배포 때 “지난번에 이거 봤나?”를 알 수 없다.
어느 코드를 확인했는지 모른다. 체크리스트를 채운 뒤 코드를 세 번 고쳤다면 그 체크는 무효인데, 문서만으로는 알 수 없다.
실행되는 물건으로 바꾼다
그래서 체크리스트를 스크립트로 옮겼다. 두 개다.
npm run manual-qa:mac # 앱을 띄우고 스크린샷을 찍는다
npm run write-qa-report # 보고서 틀을 만든다
첫 번째는 사람이 볼 준비를 대신 해 준다. 번들을 실행하고, 잠깐 기다렸다가, 화면을 캡처한다.
const bundle = resolve(repo, "target/release/bundle/macos/DesktopAIPet.app");
const outputDir = "/tmp/hermes-pet-manual-qa";
const screenshotDelayMs = 1800;
function captureScreenshot(path) {
const result = spawnSync("screencapture", ["-x", path], { encoding: "utf8" });
if (result.status !== 0) {
throw new Error(result.stderr.trim() || "screencapture failed");
}
if (!existsSync(path) || statSync(path).size === 0) {
throw new Error(`screenshot not written: ${path}`);
}
}
-x는 촬영음을 끄는 옵션이다. 그리고 파일이 생겼는지, 크기가 0이 아닌지까지 확인한다. 화면 녹화 권한이 없으면 screencapture가 조용히 빈 파일을 만드는 경우가 있다.
실행 시간에 상한도 뒀다.
const defaultDurationSeconds = 8;
const maxDurationSeconds = 30;
function parseDuration() {
const arg = process.argv.find((value) => value.startsWith("--duration="));
if (!arg) return defaultDurationSeconds;
const value = Number.parseFloat(arg.slice("--duration=".length));
if (!Number.isFinite(value) || value <= 0 || value > maxDurationSeconds) {
fail(`--duration must be > 0 and <= ${maxDurationSeconds} seconds`);
}
return value;
}
띄워 두고 잊어버리면 항상 위에 뜨는 창이 계속 방해한다. 상한이 있으면 알아서 닫힌다.
보고서에 코드 지문을 박는다
두 번째 스크립트가 실제 해결책이다. 보고서를 만들 때 지금 어느 코드인지를 같이 적는다.
const head = run("git", ["rev-parse", "--short", "HEAD"], { optional: true });
const branch = run("git", ["branch", "--show-current"], { optional: true });
const status = run("git", ["status", "--short", "--", "apps/desktop-tauri", "docs"], { optional: true });
const generatedAt = new Date().toISOString();
const screenshot = latestScreenshot();
이 네 줄이 핵심이다.
HEAD— 어느 커밋을 확인했나branch— 어느 브랜치인가status— 커밋 안 된 변경이 있나generatedAt— 언제 확인했나
세 번째가 특히 값어치가 있다. 작업 트리가 더러운 상태로 QA를 하면 그 결과는 어느 커밋에도 대응하지 않는다. 보고서에 그 사실이 남는다.
스크린샷도 가장 최근 것을 자동으로 붙인다.
function latestScreenshot() {
if (!existsSync(outputDir)) return "not captured yet";
const screenshots = readdirSync(outputDir)
.filter((name) => name.startsWith("hermes-pet-") && name.endsWith(".png"))
.map((name) => join(outputDir, name))
.sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs);
return screenshots[0] ?? "not captured yet";
}
없으면 "not captured yet"을 넣는다. 빈칸으로 두면 “안 찍었다”와 “찍었는데 못 찾았다”가 구분이 안 된다.
TODO를 남겨 두고 채우게 한다
보고서 틀에는 자동 검증 결과 자리가 TODO로 비어 있다.
npm run release:preflight: TODO(pass/fail)
npm run windows:preflight: TODO(pass/fail)
cargo clippy --workspace --all-targets -- -D warnings: TODO(pass/fail)
cargo test -p pet-core: TODO(pass/fail)
자동으로 돌려서 채울 수도 있었다. 일부러 안 했다.
사람이 직접 채우게 하면 그 결과를 실제로 본다. 자동으로 채우면 통과했다는 글자만 늘어나고 아무도 안 읽는다. 손으로 pass를 적으려면 최소한 명령을 돌리고 마지막 줄은 봐야 한다.
그리고 문서 첫머리에 이 물건의 정체를 못 박아 뒀다.
This report is a human manual QA record template,
not an automated pass decision.
자동화와 수동을 섞지 않는다
여기서 갈리는 게 하나 더 있다. 외부 요인으로 못 하는 항목이 생긴다.
- 서명 — 개발자 계정이 없으면 못 한다
- 실기기 확인 — 그 기계가 없으면 못 한다
- 온라인 감사 — 네트워크가 막혀 있으면 못 한다
이걸 “통과”로 적으면 거짓이고, 빈칸으로 두면 잊는다. 그래서 상태를 셋으로 뒀다.
| 상태 | 뜻 |
|---|---|
pass |
실제로 해 봤고 통과했다 |
blocked |
못 했다. 이유가 있다 |
| (없음) | 아직 안 봤다 — 배포 불가 |
blocked를 명시적으로 두는 게 핵심이다. 못 한 것과 안 한 것을 갈라 놓으면, 배포 결정을 내리는 사람이 무엇을 감수하는지 알고 결정한다.
정리
- 자동화가 안 되는 확인은 남는다. 문제는 사람이 매번 다르게 본다는 것
- 체크리스트를 문서로 두면 안 열어 본다. 실행되는 물건으로 만든다
- 보는 준비(빌드 실행, 스크린샷)를 스크립트가 대신하면 확인 자체에 집중한다
- 보고서에 커밋 해시·브랜치·작업 트리 상태·시각을 박아, 어느 코드를 확인했는지 남긴다
- 자동 검증 결과는 사람이 손으로 채우게 둔다. 자동으로 채우면 아무도 안 읽는다
- 못 한 항목은
blocked로 명시한다. 못 한 것과 안 한 것은 다르다
체크리스트의 값어치는 항목 개수가 아니라 빠뜨렸다는 사실이 남느냐에 있다.