[android] Android unit and instrumentation tests tutorial |
http://www.vogella.com/tutorials/AndroidTesting/article.html#what-to-test-on-android-applications
1. Introduction into Android testing
1.1. Testing Android applications
-
Android 의 unit testing 은 다음과 같이 두 가지로 나뉜다.
Local unit tests : JVM 위에서 test 가 돈다.
Instrumented unit test : Android System 을 요구하는 test
-
Local unit tests 는 Android device 에서 도는 것보다 훨씬 빠른 테스트가 가능하다.
Local unit test 를 사용하면서 Android API 를 실제로 사용할 일이 있다면, Mockito 와 같은 mocking framework 와 함께 사용 가능하다.
1.2. What to test on Android applications
-
Rule of thumb 으로 정해진 권장되는 test 범위는 아래와 같다.
70~80% 는 code base 로 진행되는 unit test
20~30% 는 functional test (실제 작동하는지 확인)
10% 정도의 다른 앱 component 와의 관계를 테스트하는 integration test
1.3. Tooling support for Android testing
-
Android Testing Support Library (ATSL) 는 google 에서 만든 프로젝트로 Android test 를 도와준다.
해당 lib 은 JUnit 4 와 호환되는 test runner (AndroidJUnitRunner), Espresso test framework, UI Automator test framework 를 제공한다.
Espresso 는 UI test 를 쉽게 해주고, UI Automator 는 app 의 functional test 를 쉽게 해준다.
-
AndroidJunitRunner 는 InstrumentationRegistery 를 통해 instrumentation API 를 접근할 수 있게 해준다.
InstrumentationRegistry.getInstrumentation() 은 현재 진행중인 Instrumentation 을 제공한다.
InstrumentationRegistry.getContext() 는 Instrumentation package 의 context 를 제공한다.
InstrumentationRegistry.getTargetContext() 는 target app 의 context 를 제공한다.
InstrumentationRegistry.getArguments() 는 해당 Instrumentation 에 전달된 Bundle 을 copy 본을 return 한다. (command line 으로 instrumentation 수행한 경우 유용하다. )
1.4. Android project organization for tests
-
다음은 test code 의 기본 directory 구조이다.
app/src/main/java 는 main app 의 소스코드
app/src/test/java 는 JVM 에서 수행되는 unit test 의 소스코드
app/src/androidTest/java 는 android device 에서 수행되어야만 하는 소스 코드
-
“error duplicate files in path” error 가 발생하면 아래와 같이 LICENSE.txt 를 제거해주는 구문을 build.gradle 에 넣어주면 된다.
android {
packagingOptions {
exclude 'LICENSE.txt'
}
...
}
2. Android unit testing
2.1. Unit testing in Android
-
Unit test 는 JVM 에서 돌며 shadow object 만 제공하는 android.jar 위에서 동작한다.
때문에 Mockito 와 같은 mocking lib 을 가져다 쓰기 좋다.
-
Folder 는 app/src/test 에 있다.
2.2. Required dependencies in the Gradle build file
-
dependencies {
// Unit testing dependencies
testCompile 'junit:junit:4.12'
// Set this dependency if you want to use the Hamcrest matcher library
testCompile 'org.hamcrest:hamcrest-library:1.3'
// more stuff, e.g., Mockito
}
2.3. Running the unit tests
2.3.1. Using Gradle
-
아레 command 로 수행한다.
gradlew test
2.3.2. Using Android Studio
-
테스트하고자 하는 소스 파일에서 우클릭 후 “Run XXTest” 를 수행한다.
2.4. Location of test reports
-
app/build/reports/tests/debug 위치에 index.html 형태로 주어진다.
2.5. Activating default return values for mocked methods in android.jar
-
기본적으로 modified android.jar 의 함수 호출은 exception 을 던지게 되어 있다.
만약 다른 동작을 하기를 바란다면 mocking framework 를 이용해서 mocking 을 해야 한다.
-
gradle 설정으로 exception 대신 default value 를 return 하도록 할 수 있다.
android {
testOptions {
unitTests.returnDefaultValues = true
}
...
}
3. Exercise: Create unit test
3.1. Preparation: Create Android project
3.2. Add JUnit dependency
3.3. Create test
-
@Test public void testConvertFahrenheitToCelsius() { float actual = ConverterUtil.convertCelsiusToFahrenheit(100); // expected value is 212 float expected = 212; // use this method because float is not precise assertEquals("Conversion from celsius to fahrenheit failed", expected, actual, 0.001); }
3.4. Run unit tests
4. Writing tests to run on the Android device
4.1. Instrumentation tests
-
Android testing API 는 Android component 와 app 의 life cycle 을 hooking 하는 것을 제공한다.
이런 hook 을 instrumentation API 라고 부르며, 이를 통해 life cycle 과 user interaction event 를 control 할 수 있다.
-
일반 환경에서는 app 이 life cycle 을 control 할 수 없고, user 가 app 을 drive 한다.
예를 들어 Android 가 activity 를 만들고 onCreate method 를 호출한다거나, user 가 button 을 눌렀을 때 code 가 호출되는 것 등을 control 할 수 없다.
Instrumentation 으로는 test code 로 이것들을 control 할 수 있다.
예를 들어 test 를 activity 가 start 된 다음에 수행하도록 한다거나 할 수 있다.
-
Instrumented unit test 는 JVM 대신 Android 단말이나 emulator 에서 수행된다.
실제 device 와 resource 에 접근을 하며, mocking framework 로 쉽게 mocking 할 수 없는 경우에 사용한다.
예를 들면 Parcelable 구현이 제대로 되었는지 확인 하는 경우가 그렇다.
-
Instrumentation-based test 는 key event, touch event 등을 test 환경에서 전달할 수 있다.
Espresso 와 같은 UI testing framework 를 통해 instrumentation API 등을 직접 호출하지 않아도 된다.
4.2. How the Android system executes tests
-
InstrumentationTestRunner 는 Android test 의 base test runner 이다.
이 test runner 는 test methods 들을 load 하고 수행한다.
Instrumentation API 를 통해서 Android system 과 통신한다
Test 를 시작하면 Android system 은 test 하에 있는 app 의 process 를 모두 kill 하고 새로운 instance 를 만든다.
그러나 app 을 시작하지는 않는다. app 시작은 test method 가 담당한다.
Test method 가 life cycle 을 관리한다.
-
test runner 는 init 과정에서 app 과 Activity 의 onCreate() 메소드를 호출한다.
4.3. Mocking objects in Android
-
Mockito 와 같은 mockito framework 가 instrumentation test 에서도 쓰일 수 있다.
이것은 Android system 의 일부를 대신할 수 있다.
4.4. Location of instrumentation tests
-
app/src/androidTest/java 아래에 있다.
4.5. Define dependencies and testInstrumentationRunner in the Gradle build file
-
defaultConfig {
..... more stuff
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
dependencies {
// Unit testing dependencies
androidTestCompile 'junit:junit:4.12'
// Set this dependency if you want to use the Hamcrest matcher library
androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
// more stuff, e.g., Mockito
}
4.6. Using the @RunWith(AndroidJUnit4.class)
-
@RunWith(AndroidJUnit4.class) 를 기술해주는 것이 추천된다.
AndroidJUnit4 는 JUnit4 를 확장한 녀석이다.
만약 Pure JUnit4 를 사용할 예정이면 위 Annotation 을 써주지 않아도 된다.
( Espresso 를 사용한다면 꼭 써주자 )
4.7. Run the tests on Android
-
아래 명령을 통해 테스트를 돌릴 수 있다.
gradlew connectedCheck
4.8. Location of test reports
-
app/build/reports/androidTests/connected/index.html 로 나온다.
4.9. How to replace the application for instrumentation tests
-
Application class 를 AndroidJUnitRunner 를 override 함으로써 만들 수 있다.
public class MockTestRunner extends AndroidJUnitRunner { @Override public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return super.newApplication(cl, MyMockApplication.class.getName(), context); } }
-
Test runner 를 build.gradle 에 명시해주어야 한다.
android {
...
testInstrumentationRunner "com.vogella.android.daggerjunitmockito.MockTestRunner"
}
5. Exercise: Write Android instrumentation test and use mocking
5.1. Create class to test
5.2. Create a new unit test
6. More on Android testing
6.1. Test groups
-
@SmallTest, @MediumTest, @LargeTest 가 test 를 구분한다.
-
IntstrumentationTestRunner 를 통해 특정 test group 만 수행할 수 있다.
android { defaultConfig { testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunnerArgument "size", “small" ... } ... }
6.2. Test filtering
-
@RequiresDevice : test 는 emulator 가 아닌 physical device 에서 수행되어야 한다.
@SdkSupress : @SDKSuppress(minSdkVersion=18)
6.3. Flaky tests
-
Android Action 은 time dependency 가 있을 수 있다.
Android 에세 한번 테스트해서 실패한다면 test 를 retry 하도록 할 수 있다.
@FlakyTest 를 써주면 된다.
tolerance attribute 를 통해서 몇번까지 test 를 수행할지 명시할 수 있다.
7. Testing Android components
7.1 Activity testing
-
ATSL 의 ActivityTestRule 을 통해 activity 를 test 할 수 있다.
Single activity 의 functional testing 을 도와준다.
@Before, @Test 로 annotate 된 method 들을 수행한다.
@After 가 수행되거나 없으면 @Test 로 마킹된 녀석들을 모두 수행한 후 테스트가 종료된다.
Test 중인 Activity 는 ActivityTestRule#getActivity() 를 통해 instance 를 가져올 수 있다.
-
ActivityTestRule#getActivityIntent 를 통해서 activity 를 시작한 intent 를 가져올 수 있다.
-
@MediumTest @RunWith(AndroidJUnit4.class) public class SecondActivityTest { @Rule public ActivityTestRule<SecondActivity> rule = new ActivityTestRule<SecondActivity>(SecondActivity.class) { @Override protected Intent getActivityIntent() { InstrumentationRegistry.getTargetContext(); Intent intent = new Intent(Intent.ACTION_MAIN); intent.putExtra("MYKEY", "Hello"); return intent; } }; @Test public void ensureIntentDataIsDisplayed() throws Exception { SecondActivity activity = rule.getActivity(); View viewById = activity.findViewById(R.id.target); assertThat(viewById,notNullValue()); assertThat(viewById, instanceOf(TextView.class)); TextView textView = (TextView) viewById; assertThat(textView.getText().toString(),is("Hello")); } }
7.2. Service testing
-
Service 를 test 하기 위해서는 ServiceTestRule 을 사용한다.
Service 의 start 와 shutdown 을 control 할 수 있다.
성공적인 starting 또는 binding 을 보장한다.
Service 는 helper method 에 의해 시작되거나 바인딩 될 수 있다.
test 가 끝나면(혹은 @After 의 수행이 끝나면) 자동으로 stop 되거나 unbound 된다.
-
IntentService 는 onHandleIntent method 호출이 끝나면 destroy 되기 때문에 위의 life cycle rule 이 적용되지 않는다.
-
@RunWith(AndroidJUnit4.class) @MediumTest public class MyServiceTest { @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); // test for a service which is started with startService @Test public void testWithStartedService() { mServiceRule.startService(new Intent(InstrumentationRegistry.getTargetContext(), MyService.class)); // test code } @Test // test for a service which is started with bindService public void testWithBoundService() { IBinder binder = mServiceRule.bindService(new Intent(InstrumentationRegistry.getTargetContext(), MyService.class)); MyService service = ((MyService.LocalBinder) binder).getService(); assertTrue("True wasn't returned", service.doSomethingToReturnTrue()); } }
7.3. Receiver testing
-
public class OutgoingCallReceiver extends BroadcastReceiver { @Override public void onReceive(Context ctx, Intent i) { if(i.getAction().equalsIgnoreCase(Intent.ACTION_NEW_OUTGOING_CALL)) { String phoneNum = i.getStringExtra(Intent.EXTRA_PHONE_NUMBER); Intent intent = new Intent(ctx, MyActivity.class); intent.putExtra("phoneNum", phoneNum); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ctx.startActivity(intent); } } }
위 코드를 테스트 하려면.. Mockito 를 통해 할 수 있다.
@Test public void testStartActivity() { // prepare data for onReceive and call it Intent intent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL); intent.putExtra(Intent.EXTRA_PHONE_NUMBER, "01234567890"); mReceiver.onReceive(mContext, intent); assertNull(mReceiver.getResultData()); // what did receiver do? ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class); verify(mContext, times(1)).startActivity(argument.capture()); Intent receivedIntent = argument.getValue(); assertNull(receivedIntent.getAction()); assertEquals("01234567890", receivedIntent.getStringExtra("phoneNum")); assertTrue((receivedIntent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0); }
7.4. Content provider testing
-
ProviderTestCase2 를 사용한다.
이는 자동으로 IsolatedContext object 를 추가해서 provider 를 init 시킨다.
이 context 는 Android system 과 별개로 독립되어 있지만, db access 는 허락한다.
IsolatedContext object 사용은 real device 에 영향을 미치지 않음을 보장한다.
-
ProviderTestCase2 는 getMockContentResolver() 를 통해 MockContentResolver 를 제공한다.
7.5. Loader testing
-
Loader testing 은 LoaderTestCase 를 사용한다.
이 녀석도 Rule 이 제공되면 좋겠지만 현재는 제공되지 않는다.
8. Application testing
-
Application class 도 logic, data, setting 을 가지고 있다.
따라서 이 녀석도 테스트 되어야 한다.
JUnit4 test 를 사용해서 JVM 에서 테스트를 한다.
이 경우에 dependency 는 모두 mock 을 사용한다.
-
Android runtime 에서 Application 을 테스트 하려면 ApplicationTestCase 를 사용하면 된다.
특정 JUnit4 rule 이 있으면 좋겠지만, 현재는 제공되지 않는다.
Android test 의 test runner 인 InstrumentationTestRunner 가 init 과정에서 자동으로 application instance 를 만든다.
onCreate 에서 async processing 을 한다면 이것도 고려해야 한다.
9. Exercise: Testing the Android application
9.1. Create project
9.2. Create unit test for application object
9.3. Create instrumented test for application object
-
public class ApplicationTest extends ApplicationTestCase<MyApplication> { private MyApplication application; public ApplicationTest() { super(MyApplication.class); } protected void setUp() throws Exception { super.setUp(); createApplication(); application = getApplication(); } public void testCorrectVersion() throws Exception { PackageInfo info = application.getPackageManager().getPackageInfo(application.getPackageName(), 0); assertNotNull(info); MoreAsserts.assertMatchesRegex("\\d\\.\\d", info.versionName); } }
10. Creating code coverage report
-
code coverage report 는 app code 중 얼만큼이 test 되는지를 보여준다.
report 를 만들려면 다른 launch configuration 을 만들어야 한다.
package 를 선택하고 우클릭해서 “Create Tests in …” 를 선택한다.
[Code coverage] 를 설정한다.
그럼 toolbar 에 해당 launch config 가 추가되고 돌리면 coverage report 가 나온다.
11. Using the Monkey tool for creating a random event stream
11.1. What is monkey?
-
pseudo random event 를 device 에 전달해주는 command line tool 이다.
event 들을 특정 package 에만 전달하도록 할 수 있다.
11.2. How to use Moneky
-
아래와 같은 명령어로 테스트 할 수 있다.
adb shell monkey -p <packageName> -v 2000
-
-s <seed> 를 통해 event 의 random 에 대한 seed 를 전달할 수 있다.
12. User interface testing with activities and fragments
-
Espresso 를 통한 UI test 와 UI Automator 를 통한 cross component testing 을 해보라.
13. Test folder creation in Android Studio
-
최신 Android Studio 는 test folder 를 자동으로 생성한다.
그러나 이전 버전의 경우 수동으로 생성해줘야 한다.
-
어래와 같이 build.gradle 의 sourceSet 에 folder path 를 추가해줘야 할 수 있다.
sourceSets { main { java.srcDirs = ['src/main/java', 'src/test/java/'] } }
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
[Effective Unit Testing] Chap1. 좋은 테스트의 약속 (0) | 2019.02.26 |
---|---|
[Gradle] compile(api) vs. implementation (1) | 2019.02.06 |
[android] Espresso Tutorial (0) | 2019.02.04 |
[android] gradle build 성능 향상시키기 (0) | 2019.02.03 |
[android] Browser 로부터 Share intent 받아 처리하기 (0) | 2019.02.02 |
댓글