요소 또는 시스템의 테스트 전략을 설계할 때 다음과 같은 세 가지 관련 테스트 측면이 있습니다.
- 범위: 테스트가 코드의 어느 정도까지 터치하나요? 테스트로 단일 메서드, 전체 애플리케이션 또는 그 사이의 지점을 검증할 수 있습니다. 테스트된 범위는 테스트 중이며 일반적으로 테스트 대상이라고 하지만 테스트 대상 시스템 또는 테스트 대상 단위라고 합니다.
- 속도: 테스트가 얼마나 빠르게 실행되나요? 테스트 속도는 밀리초에서 몇 분까지 다양할 수 있습니다.
- 충실도: 테스트가 얼마나 '실제' 환경인가요? 예를 들어 테스트 중인 코드의 일부가 네트워크 요청을 해야 한다면 테스트 코드가 실제로 이 네트워크 요청을 하는가, 아니면 결과를 가짜로 만드는 것일까요? 테스트가 실제로 네트워크와 통신한다면 충실도가 더 높다는 의미입니다. 단, 테스트 실행 시간이 더 오래 걸리거나 네트워크가 다운되면 오류가 발생하거나 사용 비용이 많이 들 수 있다는 단점이 있습니다.
테스트 전략 정의를 시작하는 방법을 알아보려면 테스트 항목을 참조하세요.
격리 및 종속 항목
요소 또는 요소 시스템을 테스트하는 경우 격리로 테스트하게 됩니다. 예를 들어 ViewModel을 테스트할 때 에뮬레이터를 시작하고 UI를 실행할 필요가 없습니다. Android 프레임워크에 종속되지 않거나 종속되면 안 되기 때문입니다.
그러나 테스트 대상이 작동하려면 다른 주체에 의존할 수 있습니다. 예를 들어 ViewModel은 데이터 저장소에 종속되어 작동할 수 있습니다.
테스트 대상에게 종속 항목을 제공해야 할 때 일반적인 방법은 테스트 더블 (또는 테스트 객체)을 만드는 것입니다. 테스트 더블은 앱에서 구성요소로 보이고 작동하는 객체이지만 특정 동작이나 데이터를 제공하기 위해 테스트에서 생성됩니다. 주요 장점은 테스트를 더 빠르고 간단하게 할 수 있다는 것입니다.
테스트 더블 유형
테스트 더블에는 다음과 같은 다양한 유형이 있습니다.
가짜 | 클래스의 '작동하는' 구현을 가지고 있지만 테스트에는 좋지만 프로덕션에는 적합하지 않은 방식으로 구현되는 테스트 더블입니다.
예: 메모리 내 데이터베이스 가짜는 모의 프레임워크가 필요 없고 가볍습니다. 권장됩니다. |
---|---|
예시 | 동작하도록 프로그래밍하는 방식으로 동작하고 상호작용에 관한 기대치를 갖는 테스트 더블입니다. 모의 스크린샷은 상호작용이 정의된 요구사항과 일치하지 않으면 테스트에 실패합니다. 모의 스크린샷은 보통 이러한 모든 목적을 달성하기 위해 모의 프레임워크로 만듭니다.
예: 데이터베이스의 메서드가 정확히 한 번 호출되었는지 확인합니다. |
스텁 | 동작하도록 프로그래밍하는 방식으로 동작하지만 상호작용에 관한 기대치는 없는 테스트 더블입니다. 일반적으로 모의 프레임워크로 만들어집니다. 편의를 위해 스텁보다 가짜가 선호됩니다. |
가짜 | 매개변수로 제공되어야 하는 경우와 같이 전달되었지만 사용되지 않는 테스트 더블입니다.
예: 클릭 콜백으로 전달된 빈 함수 |
스파이 | 모의 객체와 유사하게 일부 추가 정보도 추적하는 실제 객체의 래퍼입니다. 일반적으로 복잡성을 추가하기 위해 피합니다. 따라서 스파이보다 가짜 또는 모의 버전이 선호됩니다. |
그림자 | Robolectric에서 사용되는 가짜. |
가짜 제품 사용 예
UserRepository
라는 인터페이스에 종속되고 첫 번째 사용자의 이름을 UI에 노출하는 ViewModel을 단위 테스트하려고 한다고 가정해 보겠습니다. 인터페이스를 구현하고 알려진 데이터를 반환하여 모조 테스트 더블을 만들 수 있습니다.
object FakeUserRepository : UserRepository {
fun getUsers() = listOf(UserAlice, UserBob)
}
val const UserAlice = User("Alice")
val const UserBob = User("Bob")
이러한 모조 UserRepository
는 프로덕션 버전에서 사용할 로컬 및 원격 데이터 소스에 종속되지 않아도 됩니다. 파일은 테스트 소스 세트에 상주하며 프로덕션 앱과 함께 제공되지 않습니다.
다음 테스트는 ViewModel이 첫 번째 사용자 이름을 뷰에 올바르게 노출하는지 확인합니다.
@Test
fun viewModelA_loadsUsers_showsFirstUser() {
// Given a VM using fake data
val viewModel = ViewModelA(FakeUserRepository) // Kicks off data load on init
// Verify that the exposed data is correct
assertEquals(viewModel.firstUserName, UserAlice.name)
}
ViewModel은 테스터에서 생성하므로 단위 테스트에서 UserRepository
를 모조로 쉽게 바꿀 수 있습니다. 그러나 더 큰 테스트에서는 임의의 요소를 대체하기가 어려울 수 있습니다.
구성요소 교체 및 종속 항목 삽입
테스트에서 테스트 중인 시스템 생성을 제어할 수 없는 경우 테스트 더블의 구성요소를 교체하는 것이 더 까다로워지며 앱 아키텍처가 테스트 가능한 설계를 따라야 합니다.
앱의 전체 사용자 흐름을 탐색하는 계측 UI 테스트와 같은 테스트 더블을 사용하면 규모가 큰 엔드 투 엔드 테스트에서도 도움이 될 수 있습니다. 이 경우에는 테스트를 밀폐하는 것이 좋습니다. 밀폐 테스트는 인터넷에서 데이터 가져오기와 같은 모든 외부 종속 항목을 방지합니다. 이렇게 하면 안정성과 성능이 향상됩니다.
이러한 유연성을 수동으로 달성하도록 앱을 설계할 수 있지만 Hilt와 같은 종속 항목 삽입 프레임워크를 사용하여 테스트 시 앱의 구성요소를 교체하는 것이 좋습니다. Hilt 테스트 가이드를 참고하세요.
Robolectric
Android에서는 특별한 유형의 테스트 더블을 제공하는 Robolectric 프레임워크를 사용할 수 있습니다. Robolectric을 사용하면 워크스테이션 또는 지속적 통합 환경에서 테스트를 실행할 수 있습니다. 에뮬레이터나 기기 없이 일반 JVM을 사용합니다. 섀도라는 테스트 더블을 사용하여 뷰, 리소스 로드 및 Android 프레임워크의 기타 부분의 확장을 시뮬레이션합니다.
Robolectric은 시뮬레이터이므로 간단한 단위 테스트를 대체하거나 호환성 테스트에 사용되어서는 안 됩니다. 경우에 따라 낮은 충실도를 희생하는 대신 속도를 제공하고 비용을 절감할 수 있습니다. UI 테스트의 좋은 접근 방식은 Robolectric 및 계측 테스트와 모두 호환되도록 하고 기능이나 호환성을 테스트할 필요성에 따라 실행할 시기를 결정하는 것입니다. Espresso 및 Compose 테스트는 모두 Robolectric에서 실행될 수 있습니다.