본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android] Android unit and instrumentation tests tutorial

by 돼지왕 왕돼지 2019. 2. 5.
반응형

[android] Android unit and instrumentation tests tutorial


http://www.vogella.com/tutorials/AndroidTesting/article.html#what-to-test-on-android-applications


@flakytest, ActivityTestRule, after, android system test, android testing, android testing support library, android unit testing, android.jar, androidjunit4, androidjunitrunner, application replace, application testing, applicationtestcase, atsl, Before, build reports test, bundle copy, code coverage report, contentprovider testing, cross component testing, db access, device resource, Emulator, error duplicate files in path, espresso test framework, espresso ui test, flaky test, functional test, getactivity, getactivityintent, getarguments, getContext, getinstrumentation, getmockcontentrsolver, gettargetcontext, gradle.build, gradlew connectedcheck, gradlew test, hamcrest matcher, helper method, instrumentation api, instrumentation test, InstrumentationRegistry, InstrumentationTestRunner, instrumented test, Integration Test, intentservice, isolatedcontext, junit4, jvm test, largetest, license.txt, lifecycle hooking, loader testing, location of test reports, mediumtest, mockapplication, mockcontentresolver, mocket test, mocking, mocking framework, mockito, mockito framework, monkey test seed, onhandleintent, Parcelable, process kill, providertestcase2, pseudo random event, real device, receiver testing, requiredevice, returndefaultvalues, rule of thumb, runwith, sdksupress, servicetestrule, shadow object, single activity, smalltest, start shutdown, stop unbound, test, test filter, test group, test method load run, test report location, test runner, test 범위, ui automator, ui automator functional test, ui automator test framework, unit test, unit testing in android, user interaction event, [android] Android unit and instrumentation tests tutorial


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/'] } }




반응형

댓글