뜯어본 것공개

토스 네뷸라 디바이스 팜

가져올 기법이 0종이었습니다. 대신 남의 원칙 하나가 제 코드의 갈라진 곳을 찾아줬습니다.

기법 0종, 도입 0건입니다. 초판에서 하나 채택했던 것이 2판에서 기각됐습니다 — 근거로 삼은 실측이 맞았는데 그 호출이 제 파이프라인에 0건이었습니다. 게임 클라이언트는 화면을 네이티브 서피스 하나에 그려서 접근성 트리가 애초에 안 잡히고, 그 사실은 제 프로젝트 문서에 이미 적혀 있었습니다. 그런데 이 조사가 헛되지 않았습니다 — 이 시스템의 원칙 하나를 자로 삼아 제 코드를 재보니 관문이 두 벌이었습니다.

① 클라이언트 — 웹 콘솔 · SDK · CLI · API 직접 호출 모든 입구가 공개 API 하나만 쓴다 ② 서버 — 오케스트레이션 점유 · 배정 · 해제 분산 락 한 기기에 두 테스트가 붙는 간섭 차단 실행 요청 큐 · 러너 여러 대 실행량이 늘면 러너를 늘려 수평 확장 ③ 에이전트 — 호스트에서 도는 다리. 연결 기기를 자동 발견해 중계 iOS 는 맥 · 안드로이드는 리눅스 — 호스트가 갈린다 ④ 기기 — 1대당 상주 컨트롤러. 직접 만든 계층이고 이 글의 핵심이다
기기 수백 대를 서버실에 24시간 물려두고, 전사 누구나 API 호출 하나로 점유해 조작하는 사내 공용 인프라.

무엇을 만든 것인가

팀마다 맥미니에 폰 대여섯씩 꽂아 각자 굴리던 작은 팜들을 하나로 통합한 것이 출발점이다. 맥미니 5대·기기 15대·개발자 1명으로 시작해 1년 만에 기기 100대를 넘겼다.

사용자가 보는 건 이게 전부다.

POST /device/occupy   { platform: "android", tags: ["smoke"] }
POST /actions/click   { x: 540, y: 1200 }

“기기 하나 잡아 줘 → 거기 (540,1200)을 눌러 줘.” 그 기기가 어느 맥미니에 물렸는지도, 드라이버 세팅도 알 필요가 없다.

기존 도구를 키우는 대신 기기 계층부터 새로 만들었다

이 글에서 제일 큰 결정이다. 규모를 키울수록 기존 자동화 프레임워크의 구조적 한계에 반복해서 부딪혔고, 임시 수리보다 소유가 낫다고 판단했다.

무엇을 걷어냈나
세션 개념 세션 기동에 매번 15~40초, 규모가 커질수록 세션 실패가 급증, 관리 비용이 기기 수에 비례
데스크톱 전제 미러링 기존 도구는 “기기 화면 → 데스크톱 앱” 전제라 서버 경유 브라우저 다중 배포에 안 맞는다
USB를 독점하는 캡처 독점하면 조작과 공존이 안 된다. 다른 방식은 10~15fps 슬라이드쇼

세션을 걷어낸 자리에 상시 떠 있는 컨트롤러 + 상태 없는 HTTP 호출을 놓았다. 그리고 인터페이스를 스펙 하나로 정의해 클라이언트를 자동 생성한다.

그 소유가 만든 부수 효과가 진짜 값어치다. 자체 한글 입력기(글자가 안 깨진다), 사내 빌드 즉시 설치, 보안 정책을 드라이버 규격 안에서 강제 — 스펙을 소유하니 남의 도구가 지원해 주기를 기다릴 필요가 없다.

깨본 결과 — 13.5배는 동종 비교가 아니다

간판 수치가 이렇다. 엘리먼트 클릭이 52ms 대 702ms = 13.5배.

그런데 저자가 스스로 단서를 붙였다 — 격차의 약 80%가 기존 도구의 화면 안정 대기이고, 그 대기를 끄면 2~3배로 좁혀진다.안전장치를 켠 값과 끈 값의 비교다. 정직하게 밝힌 건 좋은데, 표에서 “13.5배”만 떼어 인용하면 틀린 말이 된다. 선택 자체는 합리적이다 — 실시간으로 보며 조작하는 용도엔 속도가 맞다. 다만 철학의 교환이지 공짜 이득이 아니다.

더 큰 결손은 따로 있다. p50만 있고 p99가 없다. 테스트 자동화를 죽이는 건 중앙값이 아니라 꼬리다. 세션·명령 실패율, 기기 가동률, 플레이키 감소폭 — 안정성 수치가 글 전체에 하나도 없다.

나머지도 적어 둔다. 효과 근거가 증언 두 건이고 측정 조건·표본이 없다. 가장 가까운 오픈소스 선행 사례를 비교 대상에서 뺐다 — “왜 그게 아니라 자체 구축인가”가 답해지지 않았다. 그리고 iOS 캡처 경로가 특정 OS 계층이라 호스트를 통일 못 하는 구조적 비용이 생기는데 비용으로 서술되지 않았다. 마지막으로 드라이버가 비공개라 제3자가 표의 수치를 검증할 수 없다.

내 환경을 재봤다 — 그리고 대상이 틀렸다

실기기 하나로 읽기 전용 항목만 쟀다(조작계는 기기 상태를 바꾸므로 제외).

구간 분해 — 병목이 어디인가 UI 덤프 2,350ms 기기 안에서 소모 — 사실상 100% 스크린샷 1,863ms 기기 안에서 소모 95% 전송 5% 프로세스 기동 왕복 73ms = 4% ⇒ “케이블로 바꾸면 빨라진다” 기각 — 최대 이득 5% · “호스트에 상시 프로세스를 띄운다” 기각 — 최대 4% 저쪽의 이득은 호스트가 아니라 기기 안에서 나온다. 호스트 최적화로 오독하면 헛수고한다 그리고 더 큰 것 — 위 UI 덤프 호출이 내 파이프라인에 0건이었다
읽기 전용 항목만 n=5~7로 측정. 전송 구간과 프로세스 기동을 따로 떼어 재보니 둘 다 병목이 아니었다.

UI 덤프가 2,350ms로 저쪽의 10배 느렸다. 그래서 초판에서 기기 안 상주 컨트롤러를 유일한 수확으로 채택했다.

2판에서 기각됐다. 코드를 전수 대조하니 제 파이프라인에 그 호출이 0건이었다.

이유는 제 프로젝트 문서에 이미 적혀 있었다. 게임 클라이언트는 화면 전체를 네이티브 서피스 하나에 그린다 — 접근성 트리가 애초에 안 잡힌다. 그래서 제 화면 조작은 스크린샷 → 비전 → 좌표 탭 루프다. 그 도구를 붙여도 빈 트리가 온다. 10배 빨라질 대상 자체가 존재하지 않는다.

2,350ms는 실측이 맞았는데 대상이 틀렸다. 벤치마크를 재기 전에 “이 호출이 내 런에서 실제로 일어나는가”를 먼저 확인했어야 했다.

같은 실측이 가설 두 개를 더 기각했다. “케이블로 바꾸면 빨라진다” — 전송 구간이 전체의 5%라 최대 이득이 5%다. “프로세스를 상시 띄워 재사용한다” — 왕복이 73ms라 최대 4%다. 저쪽의 이득은 호스트가 아니라 기기 안에서 나오는데, 그걸 호스트 최적화로 오독하면 헛수고한다.

남은 건 남의 기법이 아니라 제 쪽 규율

이 글에서 “모든 도구가 공개 API 하나로 만들어졌다”고 한 지점을 자로 삼아 제 코드를 재봤다.

게이트웨이를 우회해 명령을 직접 부르는 곳이 7개 파일 14건이었다. 래퍼를 경유하는 파일과 거의 반반이다.

그런데 더 나쁜 게 나왔다. 우회 파일이 문제가 아니라 관문 자체가 두 벌이었다.

에러 분류 실제 런이 쓰는 것
정식 관문 있음 있음
다른 한 벌 없음 없음

정식 관문의 방어가 실제 런에는 하나도 안 걸려 있었다. 나중에 계측기를 붙일 때도 이 사실 때문에 방어 없는 쪽에 달아야 했다.

판정

기법 0종, 도입 0건. 이 조사가 만든 건 제 코드에 대한 발견 하나다.

그게 벤치마킹의 정직한 결과일 때가 있다. 남의 시스템에서 베낄 게 없어도, 그 시스템의 원칙을 자로 삼아 제 걸 재보면 갈라진 데가 보인다.

기각한 것에도 재개 조건을 붙여 뒀다 — 대상이 일반 앱이 되거나, 빌드가 접근성 노드를 노출하도록 바뀔 때.