이 글은 Shubham Garg님의 글을 번역한 글입니다.
안드로이드 앱을 다른 모듈로 모듈화하는 것은 코드 유지보수 및 빌드 성능에 이점이 있는지는 모두 알고 있습니다.
따라서 이 글에서는 계측 테스트에서도 동일하게 이 이점을 가져갈 수 있는지에 대해서 얘기해보려고합니다.
안드로이드 라이브러리 모듈과 앱 모듈은 디바이스에서 실행되는 테스트를 작성할 수 있는 androidTest
와 JVM에서 로컬로 실행되는 test(unitTest)
소스 세트가 있습니다.
Robolectric같은 프레임워크를 사용하거나 안드로이드 프레임워크 의존성이 요구 될 때마다 더 빠르고 쉽게 실행하기 위해서 Mocks/Stubs 를 사용하거나 모든 테스트를 JVM에서 작성하고 싶을 때가 있습니다. 하지만 앱에서는 JVM머신으로 컴파일되지 않은 네이티브 라이브러리를 가질 수 있고 실제 디바이스에서 동작하는 테스트를 작성하고 싶을 때는 androidTest
소스 세트에서 작성해야 합니다.
androidTest
소스 세트에서 테스트 코드를 작성함으로 팀내에서 개발자와 QA 모두 앱의 다양한 시나리오를 자동화 할 수 있습니다. 스크린샷 테스트, 네트워크 테스트, E2E 워크플로 테스트,퍼포먼스 테스트, 기타 등으로 구성된 앱 모듈에서 설정된 androidTest
소스 세트는 수백개에서 수천 개의 테스트로 인해 크기를 증가시킬 수 있고 이로 인해 새로운 테스트를 위한 안드로이드 테스트 APK를 생성하는 것 자체가 일반 APK 생성 시간보다 더 많은 시간이 걸립니다. Hilt나 Dagger 같은 플러그인을 사용한다면 더 상황이 나빠질 수 있습니다. 해당 테스트가 외부 요인 없이 로컬에서 작동해야하는지, 테스트가 특정 장치나 종속성에 의존하는지에 대한 결정하는 것은 어려울 수 있습니다. 어노테이션 효과적으로 관리하기 위해 새로운 어노테이션을 도입해야 할 수도 있습니다.
이것은 androidTest 코드가 시작할 때 관리하기 어려운 이유입니다.
테스트코드를 유지보수하기 더 쉽게 만드는 방법은 androidTest
코드를 다른 gradle 모듈로 분리하는 것 입니다. 분리하는 방법 중 하나는 별도의 라이브러리 모듈을 만들어서 앱 모듈에 의존성을 포함하는 것입니다. 하지만 이렇게 하는 것은 생각보다 간단하지 않고 Manifest 머지 이슈를 마주할 수도 있습니다. 또한 main
과 test
소스 세트가 필요하지 않은 새로운 라이브러리 모듈을 만들어야합니다.
안드로이드 테스트 모듈들이 분리되는 모습입니다. 이 모듈은 오직 계측 테스트만을 포함하고 앱 이나 다른 라이브러리 모듈을 의존하고 있는 특수 모듈입니다.
안드로이드 테스트 만을 위한 모듈을 만들기 위해서는 다음과 같은 단계를 따라야합니다.
plugins {
// This is a test only module.
// All the tests inside this would run as Android Instrumentation Tests.
id 'com.android.test'
}
새로운 모듈을 만들 때 안드로이드 스튜디오의 템플릿을 사용하고 라이브러리 모듈을 시작점으로 build.gradle 파일에서 com.android.library를 com.android.test 플러그인으로 교체해줍니다.
android {
// Here I am targeting the app module.
targetProjectPath ':app'
....
// If your app module has different flavors, you will have to declare the same here.
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'junit:junit:4.13.2'
implementation 'androidx.test.ext:junit:1.1.5'
implementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation project(':app')
}
모든 테스트 코드는 androidTest
소스 세트가 아닌 main
소스 세트 안에 있어야 합니다.
class ExampleAppInstrumentedTest {
@get:Rule
val activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun testAppModuleActivity() {
Espresso.onView(ViewMatchers.withId(com.example.testonlymodule.R.id.main_activity_view))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
}
}
계측 테스트를 Gradle 커맨드를 통해 실행해볼 수 있습니다.(moduleName은 실제 모듈의 이름으로 변경해야 합니다.)
./gradlew :{moduleName}:connectedDebugAndroidTest
지금까지가 계측 테스트를 위해 테스트-Only 모듈을 설정하는 방법입니다.
전체 구현된 예제를 보려면 아래 저장소를 참고하세요.
https://github.com/shubhamgarg1/TestOnlyModule
Jacoco를 사용해서 앱의 모듈들에서 코드 커버리지를 생성할 수 있고 이런 테스트들을 헤드리스 에뮬레이터를 사용해서 실행할 수 있습니다(최소 AGP 버전 7.3.1)
테스트 모듈간에 코드를 공유하는 방법 모든 테스트 모듈들이 포함된 shared test
모듈을 생성하는것 입니다. shared test
모듈은 안드로이드가 테스트-Only 모듈이 다른 모듈에 포함하는 것을 허용하지 않으므로 라이브러리 모듈이어야 합니다.
unit test와 android Test 소스세트들 사이에서 코드를 공유할 때와 동일한 개념으로 사용됩니다. 이전에는 2개의 소스세트에 공통 패키지를 포함하여 사용할 수 있었지만 Android Studio Chipmunk에서는 더 이상 불가능합니다.
네트워크 테스트와 퍼포먼스 테스트 모듈간의 공유가 가능한 유틸리티가 있다고 가정했을 때 Share Test Utility 모듈을 만들어서 공유할 수 있습니다.
com.android.test
플러그인을 사용하여 테스트 코드를 정리하고 더 유지보수가 쉽고 유연하게 도와주는 것을 알 수 있습니다. 이런 분리된 형태는 모든 커밋 후에 일부 테스트 모듈을 실행할지, 매일 한 번 테스트 모듈을 실행할지에 대한 결정을 도와줍니다.