[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 |
댓글