테스트 기본 요소

사용자는 버튼을 누르는 것부터 기기에 정보를 다운로드하는 것까지 다양한 수준에서 앱과 상호작용합니다. 따라서 반복적으로 앱을 개발할 때 다양한 사용 사례와 상호작용을 테스트해야 합니다.

테스트용 코드 구성

앱이 확장됨에 따라 서버에서 데이터를 가져오거나, 기기 센서와 상호작용하거나, 로컬 저장소에 액세스하거나, 복잡한 사용자 인터페이스를 렌더링해야 할 수 있습니다. 앱 다양성을 고려하여 종합적인 테스트 전략을 수립해야 합니다.

반복적으로 코드 만들기 및 테스트

반복적으로 기능을 개발하는 경우 새 테스트를 작성하거나 기존 단위 테스트에 사례와 어설션을 추가하여 시작합니다. 기능이 아직 구현되지 않았으므로 처음에는 테스트에 실패합니다.

새 기능을 디자인할 때 나타나는 책임 단위를 고려하는 것이 중요합니다. 단위마다 해당하는 단위 테스트를 작성합니다. 단위 테스트는 표준 상호작용, 잘못된 입력, 사용 가능한 리소스가 없는 사례를 비롯한 단위와의 가능한 모든 상호작용을 다뤄야 합니다. 가능하면 Jetpack 라이브러리를 활용하세요. 철저히 테스트된 이 라이브러리를 사용하면 특정한 앱 동작의 유효성을 검사하는 데 집중할 수 있습니다.

테스트 개발 주기는 실패 단위 테스트 작성, 테스트를 통과할 수 있는 코드 작성 및 리팩터링으로 구성됩니다. 전체 기능 개발 주기는 더 큰 UI 기반 주기의 한 단계 내에 있습니다.
그림 1. 반복적인 테스트 기반 개발과 관련된 두 개의 주기

그림 1과 같이 전체 워크플로우에는 길고 느린 UI 기반 주기가 코드 단위 통합을 테스트하는 일련의 중첩된 반복 주기가 포함되어 있습니다. 더 짧고 빠른 개발 주기를 사용하여 단위 자체를 테스트합니다. 이 주기 집합은 앱이 모든 사용 사례를 충족할 때까지 계속됩니다.

앱을 일련의 모듈로 보기

코드를 보다 쉽게 테스트하려면 모듈 측면에서 코드를 개발하세요. 여기서 각 모듈은 사용자가 앱 내에서 완료하는 특정 작업을 나타냅니다. 이 관점은 일반적으로 UI, 비즈니스 로직 및 데이터를 나타내는 레이어를 포함하는 앱의 스택 기반 관점과 대비됩니다.

예를 들어, '작업 목록' 앱에는 작업을 만드는 모듈, 완료된 작업의 통계를 보는 모듈, 특정 작업과 관련된 사진을 찍는 모듈이 있을 수 있습니다. 이러한 모듈식 아키텍처는 관련이 없는 클래스를 분리된 상태로 유지하는 데 도움이 되며 개발팀 내에서 소유권을 할당하기 위한 자연 구조를 제공합니다.

각 모듈 주위에 잘 정의된 경계를 설정하고 앱의 규모와 복잡성이 증가함에 따라 새 모듈을 만드는 것이 중요합니다. 각 모듈에는 하나의 중점 영역만 있어야 하며 모듈 간 통신을 허용하는 API가 일관되어야 합니다. 이러한 모듈 간 상호작용을 보다 쉽고 빠르게 테스트하려면 모듈의 가짜 구현을 만들어 보세요. 테스트에서 한 모듈의 실제 구현이 다른 모듈의 가짜 구현을 호출할 수 있습니다.

그러나 새 모듈을 만들 때 당장 모든 기능을 갖추려고 너무 고집을 부리지 마세요. 특정 모듈에 하나 이상의 앱 스택 레이어가 없어도 괜찮습니다.

앱에서 모듈을 정의하는 방법과 모듈을 만들고 게시하기 위한 플랫폼 지원에 관한 자세한 내용은 Android App Bundle을 참조하세요.

테스트 환경 구성

앱에서 테스트를 만들기 위한 환경 및 종속성을 설정할 때 이 섹션에 설명된 권장사항을 따르세요.

실행 환경을 기반으로 테스트 디렉터리 구성

Android 스튜디오의 일반 프로젝트에는 테스트를 배치하는 두 개의 디렉터리가 포함되어 있습니다. 다음과 같이 테스트를 구성하세요.

  • androidTest 디렉터리에는 실제 또는 가상 기기에서 실행되는 테스트가 포함되어야 합니다. 이러한 테스트에는 통합 테스트, 엔드 투 엔드 테스트 등 JVM만으로 앱 기능의 유효성을 검사할 수 없는 테스트가 포함됩니다.
  • test 디렉터리에는 로컬 시스템에서 실행되는 테스트(예: 단위 테스트)가 포함되어야 합니다.

여러 유형의 기기에서 테스트를 실행하는 경우의 장단점 고려

기기에서 테스트를 실행하는 경우 다음 유형 중에서 선택할 수 있습니다.

  • 실제 기기
  • 가상 기기(예: Android 스튜디오의 에뮬레이터)
  • 시뮬레이션된 기기(예: Robolectric)

실제 기기는 가장 높은 충실도를 제공하지만 테스트를 실행하는 데 가장 많은 시간이 소요됩니다. 반면에 시뮬레이션된 기기는 낮은 충실도 대신 향상된 테스트 속도를 제공합니다. 그러나 플랫폼의 바이너리 리소스 및 사실적 루퍼 개선으로 시뮬레이션된 기기가 보다 사실적인 결과를 생성할 수 있습니다.

가상 기기는 충실도와 속도 간 균형을 제공합니다. 가상 기기를 사용하여 테스트하는 경우 테스트 간 설정 시간을 최소화하려면 스냅샷을 사용하세요.

테스트 더블 사용 여부 고려

테스트를 만들 때 실제 개체 또는 테스트 더블(예: 모조 개체 또는 모의 개체)을 만드는 옵션이 있습니다. 특히 테스트 중인 개체가 다음 조건 중 하나를 충족하는 경우, 일반적으로 실제 개체를 테스트에 사용하는 것이 테스트 더블을 사용하는 것보다 더 좋습니다.

  • 개체가 데이터 개체입니다.
  • 실제 개체 버전의 종속성과 통신하지 않으면 개체가 작동할 수 없습니다. 이벤트 콜백 핸들러가 좋은 예입니다.
  • 개체와 종속성의 통신을 복제하기 어렵습니다. 메모리 내 데이터베이스가 가짜 데이터베이스 결과보다 더 강력한 테스트를 제공하는 SQL 데이터베이스 핸들러가 좋은 예입니다.

특히 사용자가 소유하지 않은 유형의 인스턴스를 모의 구현하면 일반적으로 다른 사용자가 작업한 이러한 유형의 구현에 관한 복잡성을 이해한 경우에만 작동하는 불안정한 테스트가 발생합니다. 이러한 모의 개체는 최후의 수단으로만 사용하세요. 자신만의 개체를 모의 구현해도 괜찮지만 @Spy를 사용하여 주석이 달린 모의 개체가 클래스 내의 모든 기능을 스터브하는 모의 개체보다 더 높은 충실도를 제공한다는 점에 유의하세요.

그러나 테스트가 실제 개체에서 다음과 같은 유형의 작업을 하려는 경우 가짜 또는 모의 개체를 만드는 것이 좋습니다.

  • 시간이 오래 걸리는 작업(예: 대용량 파일 처리)
  • 밀폐되지 않은 작업(예: 임의의 열린 포트에 연결)
  • 만들기 어려운 구성

도움말: 라이브러리 작성자에게 문의하여 사용자가 안정적으로 의존할 수 있는 공식적으로 지원되는 테스트 인프라(예: 가짜 개체)를 제공하는지 확인하세요.

테스트 작성

테스트 환경을 구성한 후 앱의 기능을 평가하는 테스트를 작성할 수 있습니다. 이 섹션에서는 소형, 중형 및 대형 테스트를 작성하는 방법을 설명합니다.

테스트 피라미드 수준

세 개의 레이어를 포함하는 피라미드
그림 2. 앱의 테스트 모음에 포함해야 하는 세 개의 테스트 카테고리를 보여주는 테스트 피라미드

그림 2에 표시된 테스트 피라미드는 앱이 세 개의 테스트 카테고리(소형, 중형, 대형)를 포함하는 방식을 보여줍니다.

  • 소형 테스트는 한 번에 한 클래스씩 앱 동작의 유효성을 검사하는 단위 테스트입니다.
  • 중형 테스트는 모듈 내의 스택 수준 간 상호작용 또는 관련 모듈 간 상호작용의 유효성을 검사하는 통합 테스트입니다.
  • 대형 테스트는 앱의 여러 모듈에 걸쳐 사용자 여정의 유효성을 검사하는 엔드 투 엔드 테스트입니다.

소형 테스트에서 대형 테스트로 피라미드를 올라가면 각 테스트의 충실도가 증가하지만 실행 시간과 유지보수 및 디버깅에 드는 노력도 증가합니다. 따라서 통합 테스트보다 단위 테스트를 더 많이 작성하고 엔드 투 엔드 테스트보다 통합 테스트를 더 많이 작성해야 합니다. 각 카테고리의 테스트 비율은 앱의 사용 사례에 따라 다를 수 있지만 일반적으로 소형 70%, 중형 20% 및 대형 10%로 카테고리를 나누는 것이 좋습니다.

Android 테스트 피라미드에 관해 자세히 알아보려면 Google I/O 2017의 Test-Driven Development on Android 세션 동영상(1분 51초 지점부터)을 참조하세요.

소형 테스트 작성

작성하는 소형 테스트는 앱 내 각 클래스의 기능 및 계약을 철저하게 검증하는 고도로 집중된 단위 테스트여야 합니다.

특정 클래스 내에 메서드를 추가하고 변경할 때 해당하는 메서드에 관한 단위 테스트를 만들고 실행하세요. 이러한 테스트가 Android 프레임워크에 종속된 경우 기기에 구속되지 않는 통합 API(예: androidx.test API)를 사용하세요. 이와 같은 일관성을 통해 실제 기기나 에뮬레이터 없이 로컬에서 테스트를 실행할 수 있습니다.

테스트가 리소스에 의존하는 경우 앱의 build.gradle 파일에서 includeAndroidResources 옵션을 사용 설정하세요. 그러면 단위 테스트가 컴파일된 버전의 리소스에 액세스하여 더 빠르고 정확하게 테스트를 실행할 수 있습니다.

app/build.gradle

    android {
        // ...

        testOptions {
            unitTests {
                includeAndroidResources = true
            }
        }
    }
    

로컬 단위 테스트

단위 테스트가 기기 또는 에뮬레이터에서 실행될 수 있도록 가능한 경우 AndroidX 테스트 API를 사용하세요. 항상 JVM 지원 개발용 컴퓨터에서 실행되는 테스트의 경우 Robolectric을 사용할 수 있습니다.

Robolectric은 Android 4.1(API 레벨 16) 이상의 런타임을 시뮬레이션하고 그림자라는 커뮤니티에서 유지하는 가짜 개체를 제공합니다. 이 기능을 사용하면 에뮬레이터 또는 모의 개체를 사용하지 않고도 프레임워크에 종속된 코드를 테스트할 수 있습니다. Robolectric은 Android 플랫폼의 다음 측면을 지원합니다.

  • 구성요소 수명 주기
  • 이벤트 루프
  • 모든 리소스

계측 단위 테스트

실제 기기 또는 에뮬레이터에서 계측 단위 테스트를 실행할 수 있습니다. 그러나 이러한 양식의 테스트는 로컬 단위 테스트보다 실행 시간이 훨씬 더 오래 걸리기 때문에 실제 기기 하드웨어에 관해 앱의 동작을 평가해야 하는 경우에만 이 방법을 사용하는 것이 좋습니다.

계측 테스트를 실행하는 경우 AndroidX 테스트에서 다음 스레드를 사용합니다.

  • 'UI 스레드' 또는 '활동 스레드'라고도 하는 기본 스레드. 여기서 UI 상호작용 및 액티비티 수명 주기 이벤트가 발생합니다.
  • 대부분의 테스트가 실행되는 계측 스레드. 테스트 모음이 시작될 때 AndroidJUnitTest 클래스가 이 스레드를 시작합니다.

테스트가 기본 스레드에서 실행되어야 하는 경우 @UiThreadTest를 사용하여 테스트에 주석을 답니다.

중형 테스트 작성

소형 테스트를 실행하여 앱의 각 단위를 테스트하는 것 외에도 모듈 수준에서 앱 동작의 유효성을 검사해야 합니다. 이를 위해 단위 그룹의 공동작업 및 상호작용 유효성을 검사하는 통합 테스트인 중형 테스트를 작성하세요.

앱의 구조와 다음 중형 테스트 예를 사용하여(범위 증가 순서로) 앱의 단위 그룹을 표현하는 가장 좋은 방법을 정의하세요.

  1. 뷰와 뷰 모델 간의 상호작용(예: Fragment 개체 테스트, 레이아웃 XML 유효성 검사 또는 ViewModel 개체의 데이터 바인딩 로직 평가)
  2. 여러 데이터 소스 및 데이터 액세스 개체(DAO)가 예상대로 상호작용하는지 확인하는 앱 저장소 레이어의 테스트
  3. 특정 화면의 상호작용을 테스트하는 앱의 세로 슬라이스. 이러한 테스트는 앱 스택의 레이어 전반에서 상호작용을 확인합니다.
  4. 앱의 특정 영역을 평가하는 다중 프래그먼트 테스트. 이 목록에 언급된 다른 유형의 중형 테스트와는 달리 일반적으로 이 유형의 테스트에는 실제 기기가 필요합니다. 테스트 중인 상호작용에 여러 UI 요소가 포함되어 있기 때문입니다.

이러한 테스트를 실행하려면 다음 단계를 따르세요.

  1. Espresso Intents 라이브러리의 메서드를 사용합니다. 이러한 테스트에 전달하는 정보를 단순화하려면 가짜 개체 및 스텁을 사용합니다.
  2. IntentSubject와 Truth 기반 어설션을 함께 사용하여 캡처된 인텐트를 확인합니다.

계측 중형 테스트 실행 시 Espresso 사용

Espresso는 기기 또는 Robolectric에서 다음과 유사한 UI 상호작용을 실행할 때 작업을 동기화된 상태로 유지합니다.

  • View 개체에 관한 작업 실행
  • 거동이 불편한 사용자가 앱을 사용하는 방법 평가
  • RecyclerViewAdapterView 개체 내에서 활동 찾기 및 활성화
  • 발신 인텐트 상태의 유효성 검사
  • WebView 개체 내 DOM의 구조 확인

이러한 상호작용과 앱 테스트에서 이를 사용하는 방법을 자세히 알아보려면 Espresso 가이드를 참조하세요.

대형 테스트 작성

앱 내의 각 클래스와 모듈을 격리된 상태에서 테스트하는 것도 중요하지만 여러 모듈 및 기능을 통해 사용자를 안내하는 엔드 투 엔드 워크플로의 유효성을 검사하는 것도 그만큼 중요합니다. 이러한 유형의 테스트는 코드에 피할 수 없는 병목 현상을 발생시키지만 가능한 실제 완제품에 가까운 앱의 유효성을 검사하여 이 영향을 최소화할 수 있습니다.

앱이 충분히 작은 경우 앱의 기능을 전체적으로 평가하는 데 하나의 대형 테스트 모음만 필요할 수 있습니다. 그렇지 않은 경우 대형 테스트 모음을 팀 소유권, 기능 범주 또는 사용자 목표별로 나눠야 합니다.

일반적으로 실제 기기보다는 Firebase Test Lab과 같은 클라우드 기반 서비스 또는 에뮬레이션된 기기에서 앱을 테스트하는 것이 더 좋습니다. 화면 크기와 하드웨어 구성의 여러 조합을 더 쉽고 빠르게 테스트할 수 있기 때문입니다.

Espresso의 동기화 지원

Espresso는 중간 규모의 계측 테스트를 지원할 뿐만 아니라 대형 테스트에서 다음 작업을 완료할 때 동기화를 지원합니다.

  • 앱의 프로세스 경계를 교차하는 워크플로 완료. Android 8.0(API 레벨 26) 이상에서만 사용 가능합니다.
  • 앱 내의 장기 실행 백그라운드 작업 추적
  • 오프-기기 테스트 실행

이러한 상호작용과 앱 테스트에서 이를 사용하는 방법을 자세히 알아보려면 Espresso 가이드를 참조하세요.

AndroidX 테스트를 사용하여 다른 테스트 작업 완료

이 섹션에서는 AndroidX 테스트의 요소를 사용하여 앱 테스트를 더 구체화하는 방법을 설명합니다.

Truth를 사용하여 더 읽기 쉬운 어설션 만들기

Guava 팀에서는 Truth라는 유창한 어설션 라이브러리를 제공합니다. 테스트의 유효성 검사 단계 또는 그다음 단계를 구성할 때 이 라이브러리를 JUnit 또는 Hamcrest 기반 어설션 대신 사용할 수 있습니다.

일반적으로 Truth는 다음과 같은 테스트 중인 조건이 포함된 구문을 사용하여 특정 개체에 특정 속성이 있음을 나타내는 데 사용합니다.

  • assertThat(object).hasFlags(FLAGS)
  • assertThat(object).doesNotHaveFlags(FLAGS)
  • assertThat(intent).hasData(URI)
  • assertThat(extras).string(string_key).equals(EXPECTED)

AndroidX 테스트는 Truth 기반 어설션을 보다 쉽게 구성할 수 있도록 Android와 관련된 여러 추가 주체를 지원합니다.

AndroidX 테스트 API는 다음 섹션에서 논의하는 모바일 앱 테스트와 관련된 일반 작업을 실행하는 데 도움이 됩니다.

UI 테스트 작성

Espresso를 사용하면 스레드로부터 안전하게 프로그래매틱 방식으로 앱에서 UI 요소를 찾고 상호작용할 수 있습니다. 자세히 알아보려면 Espresso 가이드를 참조하세요.

UI 테스트 실행

AndroidJUnitRunner 클래스는 Android 기기에서 JUnit 3 또는 JUnit 4 스타일의 테스트 클래스를 실행할 수 있도록 하는 계측 기반 JUnit 테스트 실행기를 정의합니다. 이 테스트 실행기는 테스트 패키지와 테스트 대상 앱을 기기 또는 에뮬레이터에 로드하고 테스트를 실행하며 테스트 결과를 보고하는 과정을 도와 줍니다.

이러한 테스트의 신뢰성을 더 높이려면 자체 Instrumentation 샌드박스에서 각 UI 테스트를 실행하는 Android Test Orchestrator를 사용하세요. 이 아키텍처는 테스트 간의 공유 상태를 줄이고 테스트별로 앱 비정상 종료 문제를 격리합니다. 앱을 테스트할 때 Android Test Orchestrator에서 얻을 수 있는 이점을 자세히 알아보려면 Android Test Orchestrator 가이드를 참조하세요.

표시된 요소와의 상호작용

UI Automator API를 사용하면 포커스가 있는 활동 또는 프래그먼트에 관계없이 기기의 표시된 요소와 상호작용할 수 있습니다.

주의: 앱이 중요한 사용 사례를 처리하기 위해 시스템 UI 또는 다른 앱과 상호작용해야 하는 경우에만 UI Automator를 사용하여 앱을 테스트하는 것이 좋습니다. UI Automator는 특정 시스템 UI와 상호작용하기 때문에 플랫폼 버전 업그레이드 및 Google Play 서비스 신규 출시 이후마다 UI Automator 테스트를 다시 실행하고 수정해야 합니다.

UI Automator를 사용하는 대신 밀폐 테스트를 추가하거나 대형 테스트를 소형 및 중형 테스트의 모음으로 분리하는 것이 좋습니다. 특히 다른 앱에 정보를 전송하고 인텐트 결과에 응답하는 등 한 번에 하나의 앱 간 통신을 테스트하는 데 집중하세요. Espresso-Intents 도구는 이러한 소규모 테스트를 작성하는 데 도움이 될 수 있습니다.

일반 사용성의 유효성을 검사하기 위해 접근성 확인 추가

앱의 인터페이스를 통해 거동이 불편한 사용자를 비롯한 모든 사용자가 기기와 상호작용하고 앱에서 보다 쉽게 작업을 완료할 수 있어야 합니다.

앱 접근성 유효성 검사에 도움을 주기 위해 Android의 테스트 라이브러리는 다음 섹션에서 논의되는 여러 내장 기능을 제공합니다. 여러 유형의 사용자에 관한 앱 사용성의 유효성을 검사하는 방법을 자세히 알아보려면 앱 접근성 테스트 가이드를 참조하세요.

Robolectric

다음 코드 스니펫에서와 같이 테스트 모음의 시작 부분에 @AccessibilityChecks 주석을 포함하여 접근성 확인을 사용 설정하세요.

Kotlin

    import org.robolectric.annotation.AccessibilityChecks

    @AccessibilityChecks
    class MyTestSuite {
        // Your tests here.
    }
    

자바

    import org.robolectric.annotation.AccessibilityChecks;

    @AccessibilityChecks
    public class MyTestSuite {
        // Your tests here.
    }
    

에스프레소

다음 코드 스니펫에서와 같이 테스트 모음의 setUp() 메서드에서 AccessibilityChecks.enable()을 호출하여 접근성 확인을 사용 설정하세요.

이러한 접근성 확인의 결과를 해석하는 방법에 관한 자세한 내용은 Espresso 접근성 확인 가이드를 참조하세요.

Kotlin

    import androidx.test.espresso.accessibility.AccessibilityChecks

    @Before
    fun setUp() {
        AccessibilityChecks.enable()
    }
    

자바

    import androidx.test.espresso.accessibility.AccessibilityChecks;

    @Before
    public void setUp() {
        AccessibilityChecks.enable();
    }
    

활동 및 프래그먼트 수명 주기 시험

앱의 활동 및 프래그먼트가 시스템 수준 중단 및 구성 변경에 응답하는 방식을 테스트하려면 ActivityScenarioFragmentScenario 클래스를 사용하세요. 자세히 알아보려면 활동 테스트프래그먼트 테스트 방법에 관한 가이드를 참조하세요.

서비스 수명 주기 관리

AndroidX 테스트에는 주요 서비스의 수명 주기를 관리하기 위한 코드가 포함되어 있습니다. 이러한 규칙을 정의하는 방법을 알아보려면 JUnit4 규칙 가이드를 참조하세요.

SDK 버전에 따라 다른 모든 동작 변형 평가

앱의 동작이 기기의 SDK 버전에 따라 달라지는 경우 앱 로직을 브랜치한 방법에 따라 minSdkVersion 또는 maxSdkVersion 값을 전달하여 @SdkSuppress 주석을 사용하세요.

Kotlin

    @Test
    @SdkSuppress(maxSdkVersion = 27)
    fun testButtonClickOnOreoAndLower() {
        // ...
    }

    @Test
    @SdkSuppress(minSdkVersion = 28)
    fun testButtonClickOnPieAndHigher() {
        // ...
    }
    

자바

    @Test
    @SdkSuppress(maxSdkVersion = 27)
    public void testButtonClickOnOreoAndLower() {
        // ...
    }

    @Test
    @SdkSuppress(minSdkVersion = 28)
    public void testButtonClickOnPieAndHigher() {
        // ...
    }
    

참고 자료

Android에서 테스트하는 방법을 자세히 알아보려면 다음 자료를 참조하세요.

샘플

Codelab