# 무인 루프

> 제품이 다 통과한다는 전제를 깔면 모든 FAIL이 자동화 결함이 됩니다. 공짜 오라클입니다.

- 수치: 9h57m
- 상태: wip
- 사람이 보는 화면: https://aitryed.com/ko/built/android-qa/loop/
- 반대 언어: https://aitryed.com/en/built/android-qa/loop/index.md
- 이 사이트 안내(AI용): https://aitryed.com/ko/llms.txt

---

**사람이 안 보는 동안 밤새 돌리려면 무엇이 필요한가. 여기서 쓴 방법이 좀 특이합니다 — «이미 다 통과하는 걸로 확인된 검사»만 돌립니다. 그러면 실패가 나올 때마다 범인이 하나로 정해집니다.**

## 「공짜 판정관」이라는 발상

자동화를 만들 때 제일 어려운 게 <strong>「실패가 났는데, 제품 탓인지 자동화 탓인지 모르겠다」</strong>입니다.

둘을 가리려면 사람이 열어 봐야 합니다. 그러면 **무인이 아닙니다.**

여기서는 그 문제를 **처음부터 없앴습니다.**

> **이미 전부 통과하는 걸로 확인된 검사 목록만 돌린다.**

그러면 이렇게 됩니다.

| 결과 | 무슨 뜻인가 |
|---|---|
| 통과 | 정상 |
| **실패** | **무조건 자동화 탓** |

**사람이 안 봐도 범인이 정해집니다.** 그래서 이걸 「공짜 판정관」이라고 적었습니다. 판정해 주는 장치를 따로 만들 필요 없이, **전제 하나가 판정관 역할**을 합니다.

## 끝나는 조건을 «먼저» 적어 뒀습니다

무인으로 돌릴 때 두 번째로 어려운 게 <strong>「언제 끝내나」</strong>입니다.

안 정해 두면 **영원히 돕니다.** 아니면 **적당히 잘 되는 것 같을 때 사람이 멈춥니다** — 그러면 그게 언제였는지, 어떤 상태였는지가 안 남습니다.

그래서 시작 전에 적었습니다.

> **전 항목 기계 판정 통과 + 판정 불가 0건. 그게 «두 사이클 연속».**

「두 사이클 연속」이 중요합니다. **한 번 잘 된 건 우연일 수 있습니다.**

## 결과

**300번 · 9시간 57분 · 사람 없이 · 실패 0 · 뻗음 0.**

## 그런데 이 숫자를 성능 지표로 쓰면 안 됩니다

⚠ 정직하게 적어 둡니다.

「한 단계에 몇 초」 같은 숫자가 나오는데, **그걸 성능이라고 인용하면 안 됩니다.**

안에 **제가 넣은 «무조건 기다리기»가 섞여** 있기 때문입니다. 「여기서 3초 기다려」 같은 걸 제가 넣어 뒀습니다. 그건 **제품이 느린 게 아니라 제가 기다리게 한 것**입니다.

**제 대기 시간과 제품 시간이 섞인 숫자는 어느 쪽 지표도 아닙니다.**

## 무인을 만든 건 성능이 아니라 「복구 구조」였습니다

이게 이 글의 결론입니다.

밤새 안 멈추고 도는 비결이 **빠르게 만드는 것**일 것 같은데 아니었습니다. **실패가 났을 때 그 실패가 어디까지 번지느냐**가 결정했습니다.

| | 결과 |
|---|---|
| 한 곳이 실패하면 **전체가 멈춤** | 새벽 2시에 멈춰서 아침까지 8시간 낭비 |
| 한 곳이 실패하면 **그 하나만 실패로 적고 다음으로** | **끝까지 돌고, 아침에 실패 목록을 읽음** |

즉 **실패를 «국소적으로» 만드는 것**이 무인의 조건입니다.

그리고 이건 **빠르기와 아무 상관이 없습니다.** 느려도 안 멈추면 무인이 되고, 빨라도 한 번 멈추면 그날 밤은 끝입니다.

## 여기부터는 자세한 기록입니다

이 루프의 설계가 좀 특이하다. **검증 대상 빌드는 이미 전부 통과하는 것으로 확인된 케이스 세트**로 돈다.

## 공짜 오라클

그래서 전제를 하나 세울 수 있다.

> 제품이 ALL PASS라면, 자동화가 내는 모든 FAIL은 **자동화 자신의 결함**이다.

정답을 아는 시험지로 자동화를 채점하는 것이다. 자동화 도구를 만들 때 제일 어려운 게 **"이 실패가 제품 문제인가 내 문제인가"** 인데, 그 질문이 통째로 사라진다.

사이클은 이렇게 돈다 — 돌린다 → 진단한다 → 고친다 → 배운 걸 원장에 적는다 → 다시 돌린다.

**진단 없이 재실행하는 것은 금지다.** 이 규칙이 없으면 "한 번 더 돌려 보자"가 기본값이 되고, 그러면 간헐적으로 통과하는 케이스가 고쳐지지 않은 채 통계에만 반영된다.

## 수렴 조건을 미리 적는다

언제 끝나는지를 먼저 정해 뒀다 — **전건 기계 판정 PASS, BLOCKED 0, 그게 두 사이클 연속.**

두 사이클 연속이 중요하다. 한 번 전부 통과한 건 우연일 수 있고, **무인 루프에서 우연과 안정의 차이는 반복밖에 없다.**

그리고 이걸 미리 안 적으면 끝이 안 난다. 자동화는 항상 조금 더 고칠 데가 있어서, 종료 조건이 없으면 "아직 완벽하지 않다"로 무한히 계속된다.

## 밤새 돌린 결과

**300 사이클 · 9시간 57분 · 무인 · 실패 0 · 크래시 0.**

사이클당 검증 지점 26개가 전부 성립했고, 사이클 중앙값은 111.5초(최소 98.8 / 최대 161.8)였다. 배터리는 96%에서 95%로 떨어졌다.

배터리 이야기를 적는 이유가 있다. USB로 물려 놓고 도는데 **충전보다 소모가 빠르면 밤샘 자체가 안 된다.** 1%면 앞으로 며칠도 돌릴 수 있다는 뜻이다.

## 이 숫자를 성능 지표로 쓰면 안 된다

⚠ **단계별 수치를 성능 지표로 인용하면 안 된다.** 안에 내 스크립트의 고정 대기가 섞여 있다.

"화면 전환에 3.2초"라고 적혀 있어도 그중 2초가 내가 넣은 `sleep`이면 그건 제품 성능이 아니다. 순수하게 제품 로딩으로 읽을 수 있는 건 인게임 진입 구간 정도고, **로딩 시간을 정식 지표로 쓰려면 계측을 따로 붙여야 한다.**

이걸 구분해 적어두지 않으면 반년 뒤에 내가 내 숫자에 속는다. 그리고 더 나쁜 건, 그 숫자를 남에게 보여준 다음이다 — **한 번 인용된 숫자는 출처가 떨어져 나간 채로 돌아다닌다.**

## 무인이 되려면 실패가 국소적이어야 한다

무인 완주를 만든 건 성능이 아니라 **복구 구조**다.

무증상 정지가 제일 어려웠다. 아무것도 실패하지 않은 채로 멈추니 에러 카운터에 안 잡힌다. 그래서 **화면 변화량을 별도 축으로** 뒀다 — 변화가 없으면 단계적으로 복구를 시도하다, 그래도 안 되면 끊는다.

끊는 게 중요하다. 사람이 안 보는 실행에서 **영원히 기다리는 상태**는 실패보다 나쁘다. 실패는 로그에 남고 다음 케이스로 넘어가지만, 대기는 밤새 아무것도 안 하고 아침에 그대로 있다.
