
일주일 9시간 투자해서 수동 QA가 놓친 위젯 버그 3건을 잡았습니다. 젯팩 컴포즈(Jetpack Compose) 최근 알파에서 추가된 remote-testing 덕분이었습니다. Glance 위젯은 본문을 그대로 룰에 던질 수 없고 어댑터로 한 번 감싸야 하는데, 그 얘기는 뒤에서 다시 풀게요.
remote-testing 라이브러리가 위젯 테스트에서 의미하는 것
그동안 위젯은 테스트 사각지대였습니다. 앱과 분리된 프로세스(out-of-process)에서 렌더링되니까 createComposeRule 기반의 표준 UI 테스트가 닿지 않습니다. Glance 위젯도, 잠금화면 보조 화면도, Wear OS 표면도 사정은 비슷했습니다.
Remote Compose 라는 프레임워크가 그 사각지대를 일단 비집고 들어왔어요. UI 를 JSON 스키마나 RemoteViews 가 아니라 드로잉 오퍼레이션의 바이너리 시퀀스로 직렬화해두고, Player 가 디바이스에서 그 바이너리를 네이티브로 다시 재생하는 구조입니다. 위젯·잠금화면·Wear OS 처럼 앱 바깥에 그려야 하는 표면을 위해 만들어졌다고 보시면 되겠습니다.
이번에 최근 알파에서 추가된 androidx.compose.remote:remote-testing 은, 그 Remote Compose 문서를 instrumentation 프로세스 안에서 재생시키면서 시맨틱 트리를 끄집어내 주는 라이브러리예요. 한 마디로 onNodeWithText, performClick 같은 익숙한 단언을 위젯 UI 에도 끼울 수 있게 됐다는 얘기입니다. (alpha 트랙이라 버전 숫자가 자주 바뀌니, 아래 의존성의 정확한 최신 alpha 번호는 Remote Compose 릴리스 노트에서 한 번 확인하고 핀하시는 게 안전해요. 저는 alpha10 기준으로 작업했습니다.)
createComposeRule 류의 룰 팩토리 첫 설정과 워밍업 비용
도입 자체는 짧았습니다. build.gradle.kts 의 androidTestImplementation 한 줄 추가, 기존 createComposeRule 자리를 createComposeRule 과 유사한 룰 팩토리로 교체하는 정도였어요.
androidTestImplementation("androidx.compose.remote:remote-testing:1.0.0-alpha10")
다만 첫 실행은 좀 길었어요. 초기화 비용으로 3초 이상 잡아먹었어요. 3초가 어느 정도냐면, 한 클래스에서 테스트 12개 돌릴 때 워밍업이 매번 새로 걸리지 않게 @ClassRule 로 한 번만 잡아두는 정도의 차이가 납니다.
onNodeWithText 같은 finder 는 그대로 동작했지만, GlanceAppWidget 본문을 그대로 룰에 던질 수는 없었습니다. Player 가 재생할 수 있는 문서 형태로 한 번 감싸주는 어댑터가 필요했어요. 초기 테스트 6개 중 2개가 텍스트 노드 누락으로 실패했는데, 막상 들여다보니 그게 진짜 위젯 버그의 단서였습니다.

다른 프로세스 UI 테스트, 진입점 분리가 핵심
런처 단축키처럼 앱 프로세스 밖에서 살아 있는 UI 도 같은 룰로 테스트가 됩니다. 테스트용 Player 가 instrumentation 프로세스 안에서 트리를 가져오는 방식이라, 실제 시스템 호스팅 환경과 같은 Document 만 빌드해주면 되는 구조예요.
저 같은 경우엔 Composable 진입점을 testEntryPoint() 라는 별도 함수로 노출해서, 프로덕션 진입점과 테스트 진입점이 완전히 같은 Document 를 만들도록 묶었습니다. 이렇게 분리해두니 시스템에 올라간 위젯과 테스트가 보는 위젯이 같은 그림이라는 확신이 생겼어요.
다만 Player 의 렌더링 순서가 실제 시스템과 미세하게 어긋날 때가 있어요. onAllNodesWithTag 로 같은 태그 노드를 인덱스로 잡으면 가끔 위치가 흔들립니다. 인덱스 기반보다 의미 있는 텍스트·콘텐츠 설명 기반으로 잡는 게 안전하다고 봅니다.

remote-testing 도입 첫 주에 막혔던 지점 4가지
도입 일주일 동안 막혔던 지점은 결국 네 군데로 정리됐어요.
1. IPC 타임아웃
Document 가 준비될 때까지 기다리는 기본 타임아웃이 2초로 잡혀 있었는데, 비트맵이 무겁게 들어가는 위젯에선 자꾸 깨졌습니다. 8초로 상향 조정하고 나서야 안정됐어요. 룰 생성 단계에서 awaitReady 류의 타임아웃 옵션은 처음부터 넉넉히 잡아두시는 게 낫습니다.
2. 이미지 직렬화
Coil 의 AsyncImage 가 Player 에서 빈 공간으로 나왔어요. 비동기 로더는 테스트 Player 가 재생하는 짧은 시점에 잡히지 않거든요. 테스트용 sourceSet 만 따로 갈라서, painterResource 로 비트맵을 직접 직렬화하는 경로로 바꿨습니다.
3. 룰 충돌
기존 앱 모듈의 androidTest 와 위젯 모듈의 androidTest 가 @get:Rule 두 개를 동시에 들고 부딪혔습니다. 위젯 테스트는 별도 :widget:androidTest 모듈로 격리했더니 더 이상 안 흔들렸어요.
4. Alpha 변동성
alpha 버전 사이에 픽셀 단위 비교가 깨지는 일이 잦았어요. 픽셀 동등성은 포기하고, 시맨틱 토큰(텍스트·태그·콘텐츠 설명) 기준 비교로 전환했습니다. alpha 끝날 때까지는 토큰 기반이 안전한 길이라고 봅니다.

일주일 9시간 회고와 실제로 잡힌 위젯 버그 3건
수동 QA 가 놓쳤던 위젯 버그 세 건을 한 주 동안 잡았습니다.
- 0°C 이하 온도 표기에서 "−" 부호가 깨져 마이너스 9도가 9도로 표시되던 결함
- 시스템이 24시간제일 때도 단축키 패널의 시각 라벨이 12시간제로 고정되던 결함
- 다크 테마 전환 직후 첫 프레임에 배경색이 한 번 깜빡이던 결함
특히 1번과 3번은 사람 눈으로 잡기 어려운 대목이었어요. 첫 프레임 캡처가 일관되게 잡히는 게 최근 alpha 의 가장 큰 변화라고 보는데, 이 덕에 Paparazzi·Roborazzi 같은 스크린샷 라이브러리와 CI 에서 묶었을 때 flake 가 거의 사라졌습니다.
개인적으로는 정식 GA 까지 기다리지 말고, alpha 단계라도 위젯이 한 개 이상 들어간 프로젝트라면 토큰 기반 테스트를 먼저 끼워두는 쪽을 추천합니다. 9시간 투자에 버그 3건. 결국 이 숫자 한 줄이 alpha 도입 결정의 전부였던 셈이죠.
'Android 개발 > Jetpack Compose' 카테고리의 다른 글
| Jetpack Compose 2026.05.00 BOM 살펴보기 — MeshGradient·customizable shadows·defaultFontFamily (0) | 2026.05.31 |
|---|---|
| MeshGradient Modifier, 한 줄로 끝난다는 말의 진짜 의미 (0) | 2026.05.30 |
| Material3 1.5.0-alpha19 adaptive pane scaffold, 1.5.0 안정 직전 정리 (0) | 2026.05.19 |
| 최근 안정화된 Compose API, MeshGradient 말고 매일 쓸 3가지 (0) | 2026.05.13 |
| Jetpack Compose 업데이트, 진짜 '물건'은 따로 있었네요 (0) | 2026.05.10 |