[android] Robolectric tutorial |
Introduction
-
test 를 android emulator 나 device 에서 하는 것은 느리다.
이 환경에서는 TDD 를 이루기 어렵다.
Robolectric 은 unit test framework 로 android sdk jar 를 복제&확장해서 TDD 를 가능하도록 돕는다.
JVM 에서 android 코드를 테스트 할 수 있다.
-
@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 설정
-
testImplementation "org.robolectric:robolectric:3.8" android { testOptions { unitTests { includeAndroidResources = true } } }
Hello World
-
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 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>
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)); } }); } }
@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() 함수를 통해 접근 가능하다.
// retrieves all the toasts that have been displayed... List<Toast> toasts = shadowOf(application).getShownToasts();
-
추가적인 test api 들은 shadows 라고 불리는 특별한 class 를 통해 static method 로 접근가능하다.
// 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 을 통해 찾아질 수 있다.
@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 으로 제공할 수 있다.
@Config(resourceDir = "some/build/path/res") public class SandwichTest { @Config(resourceDir = "other/build/path/ham-sandwich/res") public void getSandwich_shouldReturnHamSandwich() { } }
-
res qualifier 도 제공할 수 있다.
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 에 넣어주면 된다.
android { testOptions { unitTests.all { systemProperty 'robolectric.dependency.repo.url', 'https://local-mirror/repo' systemProperty 'robolectric.dependency.repo.id', 'local' } } }
Device Configuration
-
qualifiers 를 이용해서 device config 를 할 수 있다.
@Test @Config(qualifiers = "fr-rFR-w360dp-h640dp-xhdpi") public void testItOnFrenchNexus5() { … }
-
properties 는 아래 링크의 테이블 참조, default 도 명시되어 있다.
http://robolectric.org/device-configuration/
-
+ 를 붙여주면 기본 세팅에 추가해서 설정을 줄 수 있다.
-
테스트 중에 device config 도 바꿀 수 있다.
@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() 를 호출한다.
Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class).create().get();
위 코드는 Activity 를 생성하고 onCreate 까지 불러준다
-
onResume 은 어떻게 하느냐?
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 를 시작하는 효과를 내려면..
Intent intent = new Intent(Intent.ACTION_VIEW); Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class, intent).create().get();
-
saved instance 를 통한 restore 는..
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 |
댓글