[android] Robolectric tutorial |
Introduction
-
test 를 android emulator 나 device 에서 하는 것은 느리다.
이 환경에서는 TDD 를 이루기 어렵다.
Robolectric 은 unit test framework 로 android sdk jar 를 복제&확장해서 TDD 를 가능하도록 돕는다.
JVM 에서 android 코드를 테스트 할 수 있다.
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @RunWith (RobolectricTestRunner. class ) public class MyActivityTest { @Test public void clickingButton_shouldChangeResultsViewText() throws Exception { MyActivity activity = Robolectric.setupActivity(MyActivity. class ); Button button = (Button) activity.findViewById(R.id.button); TextView results = (TextView) activity.findViewById(R.id.results); button.performClick(); assertThat(results.getText().toString()).isEqualTo( "Robolectric Rocks!" ); } } |
-
Robolectirc 은 view 의 inflation, resource loading, 그 외 여러 android device 에서 native C 로 구현된 것들을 다룬다.
그래서 real device 에서 수행하는 test 대부분을 JVM 에서 수행할 수 있다.
-
JVM 에서 테스트하기 때문에 dexing, packaging, emulator 나 device 에 설치 과정을 생략할 수 있어 빠른 test 와 코드 수정 등이 가능하다.
Gradle 설정
-
1 2 3 4 5 6 7 8 9 | testImplementation "org.robolectric:robolectric:3.8" android { testOptions { unitTests { includeAndroidResources = true } } } |
Hello World
-
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?xml version= "1.0" encoding= "utf-8" ?> <LinearLayout android:layout_width= "match_parent" android:layout_height= "match_parent" > <Button android:id= "@+id/login" android:text= "Login" android:layout_width= "wrap_content" android:layout_height= "wrap_content" /> </LinearLayout> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class WelcomeActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.welcome_activity); final View button = findViewById(R.id.login); button.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View view) { startActivity( new Intent(WelcomeActivity. this , LoginActivity. class )); } }); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 | @RunWith (RobolectricTestRunner. class ) public class WelcomeActivityTest { @Test public void clickingLogin_shouldStartLoginActivity() { WelcomeActivity activity = Robolectric.setupActivity(WelcomeActivity. class ); activity.findViewById(R.id.login).performClick(); Intent expectedIntent = new Intent(activity, LoginActivity. class ); Intent actual = shadowOf(RuntimeEnvironment.application).getNextStartedActivity(); assertEquals(expectedIntent.getComponent(), actual.getComponent()); } } |
Test APIs
-
Robolectirc 은 android framework 를 확장하여 internal state 를 설정 가능한 추가 기능을 제공한다.
많은 test api 들이 각각의 android class 를 확장한 형태이며, shadowOf() 함수를 통해 접근 가능하다.
1 2 | // retrieves all the toasts that have been displayed... List<Toast> toasts = shadowOf(application).getShownToasts(); |
-
추가적인 test api 들은 shadows 라고 불리는 특별한 class 를 통해 static method 로 접근가능하다.
1 2 | // simulates a new display being plugged into the device... ShadowDisplay.addDisplay("xlarge-port”); |
Configuration
-
robolectric.properties 파일을 이용해서 package-level config 를 할 수도 있고,
@Config annotation 을 이용해 class-level, method-level config 도 할 수 있다.
-
@Config annotation 은 single test class or method 에 적용할 수 있다.
Base class 들도 annotation 을 통해 찾아질 수 있다.
1 2 3 4 5 | @Config (sdk=JELLYBEAN_MR1, manifest= "some/build/path/AndroidManifest.xml" , shadows={ShadowFoo. class , ShadowBar. class }) public class SandwichTest { } |
-
robolectric.properties 는 src/test/resources 아래 package level 에 위치해야 하며 아래와 같은 내용을 담고 있다.
# src/test/resources/com/mycompany/app/robolectric.properties
sdk=18
manifest=some/build/path/AndroidManifest.xml
shadows=my.package.ShadowFoo,my.package.ShadowBar
-
전역적으로 default config 를 바꾸고 싶다면, RobolectricTestRunner 를 상속해서 buildGlobalConfig() 함수를 override 할 수도 있다.
Configurables
-
기본적으로 Robolectric 은 manifest 에 정의된 targetSdkVersion 로 테스트를 진행한다.
그러나 다른 SDK 에서 테스트하고 싶다면, sdk, minSdk, maxSdk properties 를 이용해서 test 를 진행할 수 있다.
-
Application instance 를 manifest 에 정의된 기준으로 만들려고 한다.
custom impl 을 제공하고 싶다면 application 을 제공하면 된다.
-
manifest, resource dir, asset dir 을 custom 으로 제공할 수 있다.
1 2 3 4 5 6 7 | @Config (resourceDir = "some/build/path/res" ) public class SandwichTest { @Config (resourceDir = "other/build/path/ham-sandwich/res" ) public void getSandwich_shouldReturnHamSandwich() { } } |
-
res qualifier 도 제공할 수 있다.
1 2 3 4 5 6 7 | public class SandwichTest { @Config (qualifiers = "fr-xlarge" ) public void getSandwichName() { assertThat(sandwich.getName()).isEqualTo( "Grande Croque Monégasque" ); } } |
-
system properties 도 제공할 수 있다.
robolectirc.enabledSdks - comma 로 구분된 sdk level 혹은 이름 (19,21 or KITKAT, LOLLIPOP)
robolectirc.offline - true 이면 runtime jar fetching 을 disable 시킨다.
robolectric.dependency.dir - offline mode 에서 runtime dependency 를 가진 folder 를 지정할 수 있다.
robolectric.dependency.repo.id - maven repo id 를 지정한다. (기본은 sonatype)
robolectric.dependency.repo.url - meven repo url 을 지정한다. (기본은 https://oss.sonatype.org/content/groups/public/ )
robolectric.logging.enabled = true 이면 debug logging 을 준다.
gradle 에서 system properties 는 all block 에 넣어주면 된다.
1 2 3 4 5 6 7 8 | android { testOptions { unitTests.all { systemProperty 'robolectric.dependency.repo.id' , 'local' } } } |
Device Configuration
-
qualifiers 를 이용해서 device config 를 할 수 있다.
1 2 | @Test @Config (qualifiers = "fr-rFR-w360dp-h640dp-xhdpi" ) public void testItOnFrenchNexus5() { … } |
-
properties 는 아래 링크의 테이블 참조, default 도 명시되어 있다.
http://robolectric.org/device-configuration/
-
+ 를 붙여주면 기본 세팅에 추가해서 설정을 줄 수 있다.
-
테스트 중에 device config 도 바꿀 수 있다.
1 2 3 4 5 6 7 8 9 | @Test @Config (qualifiers = "+port" ) public void testOrientationChange() { controller = Robolectric.buildActivity(MyActivity. class ); controller.setup(); // assert that activity is in portrait mode RuntimeEnvironment.setQualifiers( "+land" ); controller.configurationChange(); // assert that activity is in landscape mode } |
setQualifiers 를 통하면 system 과 app resource 를 new configuration 에 맞게 바꾼다.
그러나 activity 나 다른 component 에 어떤 action 을 가하지는 않는다.
ActivityController.configurationChange() 를 호출해서 config change 를 simulation 할 수는 있다.
Activity 에서 config change 를 대응한 경우 Activity.onConfigurationChanged() 가 불리고, 그렇지 않으면 activity 가 destroy, recreate 가 된다.
-
ShadowDisplay 를 통해서 testing 도중에 display property 를 바꿀 수 있다.
Jelly bean MR1 이상에서는 multiple display 가 가능하며, 이는 ShadowDisplayManager 를 통해서 simulation 할 수 있다.
Activity lifecycle 조종하기
-
Robolectirc 2.2 이전에는 activity 의 constructor 를 직접 호출했고, lifecycle 을 직접 호출해주었어야 한다.
그리고 ShadowActivity 의 callOnCreate 등의 함수를 이용하기도 했다.
너무 지저분하다고 생각했기 때문에 ActivityController 라는 녀석이 생겨났다.
이 녀석은 실제 Android Activity 의 lifecycle 을 흉내낸다. (자동으로 막 호출해주는건 아니다.)
Activity 를 window 에 붙이고 LayoutInflater 같은 system service 를 사용할 수 있게도 만든다.
-
ActivityController 를 보통 직접 만들지 않는다.
대신 Robolectirc.buildActivity() 를 호출한다.
1 | Activity activity = Robolectric.buildActivity(MyAwesomeActivity. class ).create().get(); |
위 코드는 Activity 를 생성하고 onCreate 까지 불러준다
-
onResume 은 어떻게 하느냐?
1 2 3 4 5 | ActivityController controller = Robolectric.buildActivity(MyAwesomeActivity. class ).create().start(); Activity activity = controller.get(); // assert that something hasn't happened activityController.resume(); // assert it happened! |
-
start(), pause(), stop(), destroy() 등의 함수도 있다.
-
Intent 로 Activity 를 시작하는 효과를 내려면..
1 2 | Intent intent = new Intent(Intent.ACTION_VIEW); Activity activity = Robolectric.buildActivity(MyAwesomeActivity. class , intent).create().get(); |
-
saved instance 를 통한 restore 는..
1 2 3 4 5 | Bundle savedInstanceState = new Bundle(); Activity activity = Robolectric.buildActivity(MyAwesomeActivity. class ) .create() .restoreInstanceState(savedInstanceState) .get(); |
-
실제 android 에서 activity 는 onCreate 이후 어느 시점까지는 window 에 붙지 않는다.
이때까지 Activity 의 view 는 visible state 가 아니다.
이 말은 이 상황에서는 click(interaction) 이 불가능하다는 것이다.
Activity hierarchy 는 onPostResume 이후에 window 에 붙는다.
이 상황을 위해 visible() 이라는 것도 있다.
이는 view 와 interaction 을 할 때 사용한다.
예를 들면 Robolectric.clickOn() 은 visible 상태가 되어야 가능하다.
Add-on modules
-
test 되는 app 의 external dependencies 를 줄이기 위해 Robolectric 의 shadow 는 여러가지 add-on package 로 구성되어 있다.
http://robolectric.org/using-add-on-modules/
위 링크를 참조하시길
Robolectric reference doc
-
http://robolectric.org/javadoc/latest/
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
Java bytecode 분석 (0) | 2018.12.19 |
---|---|
[android] AutoFill Service 란 녀석이 나타났다. (0) | 2018.12.09 |
[android] Mockito 맛보기 ( test library ) (2) | 2018.12.07 |
[android] Dagger2 tutorial part2 (0) | 2018.12.05 |
[android] Dagger2 Tutorial (0) | 2018.12.04 |
댓글