모바일 애플리케이션을 위한 스냅샷 테스팅: 작동 방식, 도구 및 예제

저자: IT Sectr 게시일: 2026-04-07 읽는 시간: 9 분

스냅샷 테스팅은 현재 화면 상태를 이전 테스트 실행에서 저장된 참조 이미지(스냅샷)와 비교하는 자동화된 사용자 인터페이스 검증 방법입니다. 시각적 불일치는 개발자의 확인이 필요한 변경 사항으로 기록됩니다. 요소의 존재 여부를 확인하는 UI 테스트와 달리, 스냅샷 테스트는 픽셀 수준의 변화(이동, 색상 편차, 레이아웃 문제)를 감지합니다. Android Developers, 2024에 따르면, 스냅샷 테스팅은 기존 UI 테스트에서 놓치는 시각적 회귀의 최대 30%를 감지하여 일관된 인터페이스를 유지하는 데 필수적인 도구입니다.

핵심 포인트

  • 스냅샷 테스팅 — 시각적 회귀를 감지하기 위해 현재 화면 렌더링을 참조 이미지와 비교하는 방법입니다.
  • Paparazzi — 에뮬레이터를 실행하지 않고 테스트 환경에서 Compose 및 View 구성 요소를 렌더링하는 Android 라이브러리입니다.
  • Shot — 다양한 해상도를 지원하며 Instrumentation 테스트에서 실제 화면의 스크린샷을 촬영하는 Android 프레임워크입니다.
  • SnapshotTesting — Point-Free의 iOS 라이브러리로, 이미지뿐만 아니라 텍스트, JSON 및 Core Data의 비교도 지원합니다.
  • 참조 업데이트 — 인터페이스의 의도적인 변경 후 한 번의 명령으로 이전 스냅샷을 새 것으로 교체합니다.

스냅샷 테스팅이란?

스냅샷 테스팅은 테스트가 UI 구성 요소를 렌더링하고, 결과 이미지를 참조로 저장한 후, 후속 실행에서 현재 렌더링을 이 참조와 비교하는 기술입니다. 이미지가 일치하면 테스트는 통과합니다. 차이점이 발견되면 테스트는 실패하고, 개발자는 변경된 픽셀이 강조 표시된 차이(diff) 이미지를 받게 됩니다. 이 기술은 웹 개발(Jest 스냅샷)에서 차용되어 모바일 플랫폼에 맞게 조정되었습니다.

스냅샷 테스트의 주요 가치는 예상치 못한 시각적 변경의 자동 감지입니다. 개발자가 전역 테마의 색상 구성을 변경하고 실수로 수십 개의 화면에 영향을 미칠 수 있습니다. 버튼과 텍스트의 존재를 확인하는 UI 테스트는 이를 알아채지 못합니다. 스냅샷 테스트는 영향을 받는 모든 화면의 모든 픽셀 변경을 캡처하여 변경 영향의 전체 그림을 제공합니다.

Mobile DevOps Summit 2023 설문 조사에 따르면, 기존 UI 테스트와 함께 스냅샷 테스팅을 사용하는 팀은 릴리스에서 시각적 결함 수를 40%까지 줄입니다. 이 접근 방식은 디자인 시스템 및 구성 요소 기반 아키텍처를 사용하는 프로젝트에서 특히 효과적이며, 하나의 기본 구성 요소 변경이 애플리케이션의 수십 개 화면에 영향을 미칠 수 있습니다.

스냅샷 테스트와 UI 테스트의 차이점

근본적인 차이는 검증 대상에 있습니다. UI 테스트는 인터페이스 요소의 존재, 상태 및 동작을 확인합니다: “버튼이 표시됨”, “텍스트에 오류 메시지가 포함됨”, “누르면 새 화면이 열림”. 스냅샷 테스트는 전체 시각적 모양을 확인합니다: 요소 배치, 여백, 색상, 글꼴, 그림자 및 둥근 모서리. 스냅샷 테스트는 “화면이 예상대로 보이는가?”라는 질문에 답하고, UI 테스트는 “화면이 예상대로 작동하는가?”에 답합니다.

실행 속도도 다릅니다. UI 테스트는 에뮬레이터나 실제 기기에서 실행되며, 전체 애플리케이션 로딩이 필요하고 시나리오당 10초에서 1분이 소요됩니다. Paparazzi와 같은 라이브러리를 사용하는 스냅샷 테스트는 에뮬레이터를 시작하지 않고 가상 환경에서 구성 요소를 렌더링하여 테스트 시간을 100~500밀리초로 단축합니다. 전체 스냅샷 테스트 세트(50~100개 화면)는 2~5분 안에 실행되며, 이는 비교 가능한 UI 테스트 세트의 30~60분보다 훨씬 빠릅니다.

그러나 스냅샷 테스트가 UI 테스트를 대체하지는 않습니다. 최적의 전략은 조합입니다: 스냅샷 테스트는 시각적 회귀(기본 상태에서 각 화면 렌더링)를 담당하고, UI 테스트는 동작 측면(클릭 시나리오, 입력 검증, 탐색)을 담당합니다. 이 조합은 최소한의 CI 실행 시간으로 인터페이스 정확성에 90%의 확신을 제공합니다.

스냅샷 테스팅 도구

Android에서는 주요 도구로 Paparazzi와 Shot이 있습니다. Cash App의 Paparazzi는 Layoutlib 중력 레이아웃을 사용하여 에뮬레이터 없이 JVM 테스트 환경에서 구성 요소를 렌더링합니다. Karumi의 Shot은 실제 기기나 에뮬레이터에서 Instrumentation 스크린샷을 촬영하고 AShot 라이브러리를 통해 참조와 비교하며, 해상도와 픽셀 밀도의 차이를 고려합니다.

Android용 Paparazzi

Paparazzi는 에뮬레이터 실행이 필요하지 않습니다. 렌더링은 Layoutlib을 통해 JVM에서 수행되어 단위 테스트에 필적하는 속도를 제공합니다. 이 라이브러리는 View 시스템과 Jetpack Compose를 모두 지원합니다. Compose의 경우 paparazzi.snapshot { MyComposable() } 수정자를 사용합니다. 참조는 src/test/snapshots에 저장되며 실행 시마다 자동으로 비교됩니다. 최대 차이 백분율은 maxPercentDifference를 통해 구성됩니다.

iOS용 SnapshotTesting

Point-Free의 SnapshotTesting은 UIImage뿐만 아니라 문자열, JSON, Data 및 전체 Core Data 저장소의 비교도 지원합니다. 이는 UI 스냅샷뿐만 아니라 JSON 응답의 직렬화 및 디코딩 검증에도 사용할 수 있는 다목적 도구입니다. SwiftUI의 경우 .image(on: .iPhone13) 수정자와 함께 assertSnapshot 확장을 사용합니다. record: true 전략은 첫 번째 실행 시 참조를 생성합니다.

크로스 플랫폼 접근 방식

React Native의 경우 jest-image-snapshot과 결합된 react-native-testing-library가 인기 있는 솔루션입니다. 스냅샷 테스팅의 웹 접근 방식은 Node.js 환경에서 구성 요소를 렌더링한 후 가상 DOM의 JSON 스냅샷을 비교하여 모바일 환경으로 이식됩니다. 이 접근 방식은 네이티브보다 빠르지만 정확도가 떨어집니다. 플랫폼별 글꼴 렌더링 및 시스템 구성 요소 특성을 고려하지 않습니다. Flutter의 경우 goldens 툴킷을 통한 골든 테스팅이 사용됩니다.

스냅샷 테스트 코드 예제

Android(Paparazzi) 및 iOS(SnapshotTesting)용 스냅샷 테스트를 살펴보겠습니다. 두 예제 모두 아바타, 이름 및 상태가 있는 사용자 카드 구성 요소의 모양을 검증합니다. 테스트는 구성 요소를 렌더링하고 테스트 데이터와 함께 결과를 리포지토리에 저장된 참조 이미지와 비교합니다.

Android: Paparazzi를 사용한 스냅샷 테스트

Paparazzi는 @Test 어노테이션과 snapshot() 메서드를 사용하여 렌더링을 캡처합니다. 참조는 src/test/snapshots 폴더에 저장되고 다음 실행 시 비교를 위해 자동으로 로드됩니다.

kotlin
class UserCardSnapshotTest {

    @get:Rule
    val paparazzi = Paparazzi(
        theme = "Theme.MyApp",
        maxPercentDifference = 0.1
    )

    @Test
    fun userCard_defaultState() {
        val card = UserCard(
            name = "Alice Johnson",
            status = "Online",
            avatarUrl = "https://example.com/avatar.png"
        )
        paparazzi.snapshot(card)
    }

    @Test
    fun userCard_offlineState() {
        val card = UserCard(
            name = "Bob Smith",
            status = "Offline",
            avatarUrl = null
        )
        paparazzi.snapshot(card, name = "user_card_offline")
    }
}

iOS: SnapshotTesting을 사용한 스냅샷 테스트

SnapshotTesting은 assertSnapshot 내에서 .snapshot() 수정자를 사용합니다. 라이브러리는 자동으로 형식을 결정합니다 — UIView의 경우 UIImage, 텍스트의 경우 String, 바이너리 데이터의 경우 Data입니다.

swift
import SnapshotTesting
import XCTest

class UserCardSnapshotTests: XCTestCase {
    func testUserCardDefaultState() {
        let card = UserCardView(
            name: "Alice Johnson",
            status: "Online",
            avatarURL: URL(string: "https://example.com/avatar.png")
        )
        let controller = UIHostingController(rootView: card)
        assertSnapshot(matching: controller, as: .image(on: .iPhone13))
    }

    func testUserCardOfflineState() {
        let card = UserCardView(
            name: "Bob Smith",
            status: "Offline",
            avatarURL: nil
        )
        assertSnapshot(matching: card, as: .image(on: .iPhone13))
    }
}

스냅샷 테스팅 워크플로

일반적인 워크플로는 네 단계로 구성됩니다. 첫 번째 실행(레코드 모드): 모든 스냅샷 테스트가 레코드 모드로 실행되어 참조 이미지가 생성되고 리포지토리에 저장됩니다. 이 단계는 초기 테스트 설정 중이나 인터페이스의 의도적인 변경 후에 수행됩니다. 레코딩 후 참조는 코드와 함께 커밋되어 프로젝트의 일부가 됩니다.

후속 실행에서 테스트는 비교 모드로 작동합니다: 각 새 렌더링이 참조와 비교됩니다. 차이점이 발견되면 diff 이미지가 생성됩니다: 참조와 일치하는 픽셀은 녹색으로, 다른 픽셀은 빨간색으로 강조 표시됩니다. 개발자는 diff를 검토하고 결정을 내립니다: 변경이 예상된 경우(의도적인 디자인 변경) 레코드 명령으로 참조가 업데이트되고, 예상치 못한 경우 버그가 수정됩니다. 참조 업데이트는 한 번의 명령으로 수행됩니다: Paparazzi의 경우 `./gradlew recordPaparazzi`, SnapshotTesting의 경우 `assertSnapshot(record: true)`입니다.

Spotify Engineering Blog(2022)에 따르면, 설명된 워크플로를 사용하는 팀은 diff 이미지 분석에 테스트당 평균 2분을 소비합니다. 50개의 스냅샷 테스트 세트의 경우 전체 참조 업데이트 주기는 15~20분이 소요되며, 이는 50개 화면에서 시각적 변경을 수동으로 확인하는 것보다 훨씬 빠릅니다.

스냅샷 테스트의 제한 사항 및 안티패턴

스냅샷 테스트에는 근본적인 제한 사항이 있습니다. 환경 민감성: 동일한 구성 요소도 OS 버전, 화면 밀도 및 글꼴 구성에 따라 다르게 렌더링될 수 있습니다. 한 머신에서 생성된 참조는 CI 서버의 렌더링과 다를 수 있습니다. 해결책은 고정된 환경 매개변수를 사용하는 것입니다: Paparazzi의 경우 특정 Layoutlib 버전, SnapshotTesting의 경우 정확한 기기 모델을 지정합니다.

안티패턴 #1: 거대한 스냅샷 — 전체 화면을 캡처하는 스냅샷 테스트는 모든 구성 요소의 사소한 변경에도 실패합니다. 올바른 접근 방식은 개별 구성 요소(버튼, 카드, 입력 필드)를 분리하여 테스트하는 것입니다. 각 구성 요소는 독립적으로 테스트되어 변경 소스를 정확히 식별할 수 있습니다. 안티패턴 #2: diff 무시 — diff 이미지를 분석하지 않고 참조를 자동으로 업데이트하면 스냅샷 테스트의 가치가 사라집니다. 각 diff에는 개발자의 의식적인 결정이 필요합니다.

Better Engineering Blog(2023)에 따르면, 스냅샷 테스트는 디자인 시스템 구성 요소와 주요 화면을 기본 상태(비어 있음, 채워짐, 오류, 경계)에서 커버할 때 가장 큰 가치를 제공합니다. 스냅샷 테스트로 애니메이션과 동적 상태를 커버하는 것은 렌더링의 타임스탬프가 비결정적이기 때문에 비효율적입니다 — 이러한 시나리오에는 비디오 녹화 또는 수동 QA 확인이 더 적합합니다.

자주 묻는 질문

스냅샷 테스트가 UI 테스트를 대체합니까?

아니요, 스냅샷 테스트는 시각적 모양을 확인하고 UI 테스트는 인터페이스 동작을 확인합니다. 최적의 전략은 두 접근 방식을 결합하는 것입니다: 시각적 회귀에는 스냅샷, 시나리오 및 탐색에는 UI 테스트를 사용합니다. 스냅샷은 “올바르게 보이는가?”에 답하고, UI 테스트는 “올바르게 작동하는가?”에 답합니다.

참조 이미지는 얼마나 자주 업데이트해야 합니까?

참조는 새로운 테마 색상, 여백 변경, 요소 추가 또는 제거 등 의식적인 디자인 변경 시마다 업데이트됩니다. 업데이트는 레코드 모드를 통해 수행되며, 이후 변경 사항이 디자이너의 기대와 일치하는지 확인하기 위해 코드 리뷰에서 diff 이미지를 검토합니다.

어떤 구성 요소를 스냅샷 테스트로 커버해야 합니까?

첫째로 디자인 시스템 구성 요소 — 버튼, 카드, 입력 필드, 모달 창입니다. 그다음 기본 상태의 주요 화면입니다. 스냅샷으로 테스트하지 마십시오 애니메이션, WebView, 지도 및 동적 콘텐츠가 있는 화면 — 비결정성으로 인해 스냅샷이 거짓 실패를 생성합니다.

다른 OS 버전으로 인한 거짓 실패를如何处理합니까?

레코드 및 테스트 모드 모두에 동일한 API Level을 사용하십시오. Paparazzi의 경우 구성에서 특정 Layoutlib 버전을 지정하십시오. SnapshotTesting의 경우 기기 모델을 고정하십시오. Android 14에서 생성된 참조는 시스템 글꼴 및 Material 테마의 변경으로 인해 Android 12의 렌더링과 다를 수 있습니다.

CI에서 스냅샷 테스트 — 설정 방법은?

CI에서 스냅샷 테스트는 검증 모드(verify)로 실행됩니다. 테스트가 실패하면 CI는 빌드 아티팩트에 diff 이미지를 표시합니다. 레코드 모드(참조 업데이트)는 개발자가 로컬에서 수행하거나 수동 트리거가 있는 별도의 CI 작업에서 수행됩니다. 참조 이미지는 리포지토리에 커밋되어야 합니다.

요약

  • 스냅샷 테스팅은 현재 구성 요소 렌더링을 참조 이미지와 비교하여 픽셀 변경 및 시각적 회귀를 감지합니다.
  • Paparazzi — 에뮬레이터 없는 빠른 Android 도구; SnapshotTesting — Point-Free의 다목적 iOS 라이브러리입니다.
  • 스냅샷 테스트는 UI 테스트를 대체하지 않고 보완합니다: 시각적인 것은 스냅샷, 동작은 UI 테스트가 담당합니다.
  • 레코드 모드는 참조 이미지를 생성하고, 검증 모드는 현재 렌더링과 비교하여 불일치 시 diff를 생성합니다.
  • 디자인 시스템 구성 요소는 스냅샷 테스트의 우선 대상이며, 변경 시 여러 화면에 영향을 미칩니다.
  • 각 diff는 개발자의 의식적인 결정이 필요합니다 — 분석 없이 참조를 자동 교체하면 테스트 가치가 사라집니다.
  • 참조 이미지는 리포지토리에 커밋되어 테스트 소스 코드와 함께 코드베이스의 일부가 됩니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기