디바이스가 필요 없는 안드로이드 UI 테스트 — Snapshot Test

유닛 테스트로 작동하는 스냅샷 테스트 라이브러리 “paparazzi” 소개

Ji Sungbin
성빈랜드
13 min readAug 22, 2022

--

Photo by NordWood Themes on Unsplash

이번 글에서는 성빈랜드 처음으로 안드로이드 테스트에 대해 다뤄보려고 합니다. 안드로이드 유닛 테스트는 이미 많은 자료들이 있으니 안드로이드 UI 테스트에 대해 작성해 보겠습니다. 하지만 이미 UI 테스트들도 많은 자료들이 있습니다. 따라서 이 글에서는 “에뮬레이터나 실기기 연결 없이 유닛 테스트 처럼 작동되는 UI 테스트” 에 대해 다룹니다.

Snapshot Test

이번 글에서 다룰 UI 테스트는 Snapshot Test 혹은 Screenshot Test 라고 불립니다. 이 글에서는 Snapshot Test 라고 부르겠습니다. 스냅샷 테스트는 일반 UI 테스트와 비해 검증하고자 하는 대상이 다릅니다. 일반 UI 테스트는 특정 상황을 주어주고 해당 상황에 맞게 UI 가 잘 작동되는지를 검증하는 반면, 스냅샷 테스트는 내가 건들지 않은 부분들은 변경 사항이 없는지를 검증합니다.

스냅샷 테스트는 현재 UI 상태를 캡처해두고 추후 UI 에 변동이 생긴다면 기존에 캡처한 UI 와 비교하여 내가 원하는 부분에만 변화가 생겼는지 검사하는 식으로 작동합니다. 검사를 진행하면서 비교할 원본 대상이 되는 캡처를 golden image 라고 합니다. 이 golden image 가 현재 UI 의 캡처와 다르다면 테스트가 실패하게 됩니다. 만약 의도된 변경이라면 해당 의도된 변경의 UI 캡처로 golden image 를 업데이트하게 됩니다.

간단하게 스냅샷 테스트에 대해 알아보았습니다. 그렇다면 스냅샷 테스트는 왜 필요할까요?

  1. 캡처한 UI 상태를 PNG 이미지 형태로 저장하여 해당 이미지 파일을 디자이너분께 바로 전달하여 좀 더 정확하게 UI 를 검증할 수 있습니다.
  2. 내가 의도하지 않은 UI 에 변화가 생겼는지를 검사할 수 있습니다. 예를 들어 안드로이드의 디자인 시스템은 기본적으로 머터리얼을 사용합니다. 만약 머터리얼이 업데이트 되면서 Checkbox 를 그리는 방식이 달라졌다면 머터리얼 changelog 를 보지 않는 이상 알아차기리 쉽지 않습니다 (Checkbox 의 사용 비중이 앱 내에서 크지 않다고 가정합니다). 의도치 않은 Checkbox 의 변화를 알아차리기 위해선 직접 Checkbox 가 쓰이고 있는 UI 에 도달해야 합니다. 하지만 스냅샷 테스트를 이용한다면 Checkbox 가 변하기 전의 golden 과 비교하여 쉽게 변경을 알아차릴 수 있습니다.

이 밖에도 크게 2가지의 장점이 더 있지만 이 장점들은 나중에 언급하도록 하겠습니다. 현재 안드로이드에서 스냅샷 테스트를 이용하기 위한 대표적인 라이브러리는 airbnb 의 Showkase, facebook 의 screenshot-tests-for-android(정직한 이름이네요 😅), pedrovgs 의 Shot, cashapp 의 paparazzi 가 있습니다. Showkase, screenshot-tests-for-android, Shot 은 모두 테스트를 실행하기 위해 에뮬레이터나 실기기가 필요합니다. 하지만 paparazzi 를 사용한다면 에뮬레이터나 실기기 없이 유닛 테스트 단에서 아래와 같이 테스팅이 가능합니다.

만약 UI 테스트를 위해 에뮬레이터가 필요한 상황이였다면 에뮬레이터를 키는 시간까지 테스트 시간에 포함돼 테스트의 결과를 보기까지 오랜 시간이 걸렸겠지만, paparazzi 를 이용한 테스트는 유닛 테스트 단에서 작동되기 때문에 UI 를 실행하고 테스트 하는데 까지 몇 초 밖에 걸리지 않습니다. 또한 사진에서 볼 수 있듯이 paparazzi 가 캡처한 이미지들은 같은 test 폴더의 snapshots/images 폴더에 저장됩니다. 직접 확인하는 UI 테스트의 경우에는 내가 확인하고 싶은 UI 를 보기 위해, 기기 실행 후 해당 UI 가 보여지는 depth 까지 매번 클릭하여 들어가야 하지만, paparazzi 를 이용한다면 내가 원하는 UI 가 캡처된 이미지 파일을 바로 클릭하는 것으로 원하는 UI 의 결과 확인이 가능합니다. 이렇게 paparazzi 를 이용하여 스냅샷 테스트를 진행하는 것만으로도 많은 UI 테스트의 시간을 줄일 수 있습니다. (스냅샷 테스트의 3번째 장점)

paparazzi under the hood

이렇도록 신기한 paparazzi 는 내부에서 어떻게 작동될까요? 정답은 layoutlib 을 이용하여 구현됩니다. layoutlib 은 안드로이드 스튜디오에서 XML preview 를 렌더링하는데 사용되는 안드로이드 뷰 시스템의 preview 용 프레임워크 입니다. paparazzi 는 layoutlib 과 layoutlib 에 리플렉션 접근을 통해 구현됐습니다. 즉, layoutlib 이 변경된다면 paparazzi 또한 작동이 멈출 가능성이 있습니다. 이 밖에도 몇몇 문제점이 있습니다.

  1. SDK 버전 33 이상을 타겟으로 하면 layoutlib 의 Bridge 클래스를 초기화하지 못해 빌드 에러가 발생합니다. 이 문제는 paparazzi 의 컴파일 SDK 버전을 32 로 변경하는 것으로 해결이 가능합니다. (#489)
  2. 항상 전체 화면으로 캡처합니다. 액티비티에 들어갈 전체 화면 자체를 캡처하는 경우에는 문제가 없지만, 세부 UI 컴포넌트를 단독으로 캡처하는 경우에는 디바이스 프레임이 배경으로 들어가서 디바이스 전체 사이즈로 캡처됩니다. 이 문제는 paparazzi 에서 캡처할 때 사용하는 가상 디바이스의 높이를 1로 제한하는 것으로 height 는 wrap_content 로 만듦으로써 50% 해결이 가능합니다. 50% 해결인 이유는 width 는 항상 match_parent 로 캡처됩니다. (#383)
  3. paparazzi 에서 안드로이드 리소스를 가져오는 구현의 한계로 library 모듈에만 작동합니다. 즉, app 모듈 하나로 구성된 단일 모듈 프로젝트에서는 paparazzi 를 사용할 수 없습니다. (#105)

현재 paparazzi 의 최신 버전은 1.0.0 이며, 마지막 문제점을 해결하기 위해 리펙토링을 진행해서 2.0.0 버전을 릴리즈 하겠다는 공지가 있었습니다. 2.0.0 버전에서는 위 3가지의 문제점들이 다 해결될 수 있기를 기대하고 있습니다. (#524)

paparazzi 사용해보기

지금까지 스냅샷 테스트를 왜 해야 하는지와 디바이스 없이 UI 테스트를 진행할 수 있게 해주는 paparazzi 에 대해 알아보았습니다. 이제 직접 paparazzi 를 사용해 보도록 하겠습니다.

간단하게 사용할 라이브러리 모듈에 paparazzi 플러그인을 추가하는 것으로 사용 설정은 끝납니다.

이후 Unit Test 폴더에 테스트를 작성해 주면 됩니다.

위에서 말한 문제점들을 해결하기 위해 paparazzi 일부 옵션들을 조정하여 paparazzi 를 초기화 해주고, paparazzi.snapshot(name: String? = null, composable: @Composable () -> Unit) 을 이용해 컴포저블 스냅샷을 찍을 수 있습니다. 물론 일반 XML 뷰도 paparazzi.snapshot(view: View, name: String? = null) 을 이용해 스냅샷을 찍을 수 있습니다. snapshot 함수 인자로 들어가는 name 은 스냅샷 이미지 파일의 파일명을 결정하는데 사용되며, 스냅샷 이미지 파일명은 아래와 같이 결정됩니다.

Snapshot.kt

스냅샷 테스트 코드를 작성해 주고 ./gradlew recordPaparazziDebug로 스냅샷을 찍을 수 있습니다. 이렇게 해서 생성된 스냅샷 이미지가 golden image 가 됩니다. 이후 ./gradlew verifyPaparazziDebug 를 이용해서 현재 UI 의 스냅샷과 golden image 를 비교할 수 있습니다. 만약 현재 UI 의 스냅샷이 golden image 와 다르다면 AssertionError 와 함께 얼마나 다른지 출력되고 다른 부분이 강조되어 이미지로 저장됩니다.

java.lang.AssertionError: Images differ (by 2.6%) - see details in file:///Users/jisungbin/AndroidStudioProjects/duckie-quack-quack/ui-components/out/failures/delta-team.duckie.quackquack.ui_QuackButtonSnapshot_default material button.pngThumbnail for current rendering stored at file:///Users/jisungbin/AndroidStudioProjects/duckie-quack-quack/ui-components/out/failures/team.duckie.quackquack.ui_QuackButtonSnapshot_default material button.png
왼쪽부터 예상한 스냅샷(golden image), 스냅샷의 다른 부분, 실제 스냅샷
Default Material Button vs Default Material Button!!!

가운데 부분을 보면 이렇게 어떤 영역이 다른지를 서로 겹쳐서 보여주고 있습니다.

보너스: paparazzi + Showkase

여기서 글을 마치긴 아쉬워서 paparazzi 의 훌륭한 usage 을 하나 소개시켜드릴려고 합니다. 위에서 Showkase 는 디바이스가 필요한 UI 테스트 라이브러리라고 언급하였습니다. 하지만 이 단점을 극복할 수 있는 제가 생각하는 Showkase 의 매력은 다양한 디자인의 variant 를 보일러플레이트 없이 annotation processor 를 이용한 자동 테스트 인거 같습니다. Showkase 를 이용해 다양한 디자인 variant 를 annotation processor 로 생성하기만 하고, UI 테스트는 paparazzi 를 이용한다면 Showkase 의 보일러플레이트 없는 다양한 디자인의 variant 테스트와 paparazzi 의 유닛 테스트를 통한 UI 테스트 장점을 모두 누릴 수 있습니다.

Showkase

아주 간단하게 Showkase 에 대해 알아보겠습니다.

위 코드와 같이 Color 와 Typography 등등 디자인 요소들에 @Showkase~ 어노테이션을 달아주면 해당 variant 들이 ShowkaseElementsMetadata 에 등록됩니다. 이후 Showkase.getMetadata() 를 통해 ShowkaseElementsMetadata 에 등록된 variant 들을 사용할 수 있습니다.

ShowkaseElementsMetadata 와 JUnit 의 parameterized test 를 이용(이 글에서는 google 의 TestParameterInjector 를 사용했습니다)해서 다양한 variant 의 UI 를 스냅샷으로 캡처할 수 있습니다.

위 테스트의 스냅샷으로 아래와 같이 4가지의 파일이 나오게 됩니다.

이런식으로 paparazzi + Showkase + ParameterizedTest 를 이용한다면 정말 쉽게 여러 variant 디자인을 테스트할 수 있습니다. 덕분에 font scale, 다크 모드, 폴더블 디바이스 등등 여러 분야 UI 테스트를 정말 쉽게 진행하게 됩니다. (스냅샷 테스트의 4번째 장점)

덕키에서의 사용

제가 리드로 참여하고 있는 덕키 프로젝트에서는 UI label 이 포함된 PR 이 올라오면 UI 스냅샷을 CI 과정에서 만들어서 quack-ui.duckie.team 에 자동 배포하고, 추후 디자이너분이 배포된 UI 를 확인하고 문제가 없다면 merge 하는 식으로 진행하고 있습니다. (어제 도입한 과정이라 아직 올라온 스냅샷은 없는점 양해 부탁드립니다 😅) merge 가 된 이후에는 CD 가 진행되면서 새로운 APK 가 팀에게 전달됩니다.

paparazzi 는 강조해서 말하듯이 유닛 테스트 단위에서 작동되기 때문에 CI 에서도 정말 쉽게 사용이 가능합니다.

먼저 ./gradlew recordPaparazziDebug 를 통해 스냅샷을 생성한 이후, ./gradlew configurationUiComponentsSnapshotDeploy 를 실행해서 생성된 스냅샷의 리스트를 생성합니다.

다음으로 CI 에서 스냅샷이 잘 생성됐는지 안심용으로 확인하기 위해 ./gradlew printGenerateSnapshotFiles 를 실행합니다.

이 과정이 끝나면 CI 로그에 이렇게 귀여운 소가 생성된 스냅샷 파일들을 알려줍니다. 다음으로 생성된 스냅샷 파일들과 스냅샷 파일의 리스트를 UI 스냅샷 배포용 저장소로 푸시하는 과정을 시작합니다.

sungbinland/duckie-quack-ui-components-snapshot-deploy

그러면 배포용 저장소가 새로운 스냅샷 파일들로 깃허브 페이지를 재구성하여 자동 배포가 진행됩니다. 마지막으로 성공적으로 배포됐다면 배포된 주소를 해당 PR 의 comment 로 남기면서 CI 과정이 종료됩니다.

원래는 이렇게 딱 할 말만 남기게 했는데 이러니까 너무 심심해서 neo-cowsay 액션을 이용하여 랜덤 아스키 아트와 함께 남기도록 바꿨습니다.

짜잔 🐢

끝!

이번 글에서는 paparazzi 를 통한 디바이스가 필요 없는 UI 테스트에 대해 소개해 드렸습니다. 이 글이 스냅샷 테스트(스크린샷 테스트) 관련해서 한국어로 올라온 첫 번째 글이 됐습니다! 🥳 이 글이 여러분들의 UI 테스트에 도움이 됐길 바랍니다. 감사합니다.

안드로이드 개발자 분들을 위한 카카오톡 오픈 채팅방을 운영하고 있습니다.

--

--

Ji Sungbin
성빈랜드

Experience Engineers for us. I love development that creates references.