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

[android] Robolectric tutorial

by 돼지왕 왕돼지 2018. 12. 8.
반응형

[android] Robolectric tutorial



http://robolectric.org/


@Config, @Config annotation, activity lifecycle, ActivityController, all block, android test on jvm, application, buildActivity with intent, buildGlobalConfig, callOnCreate, class-level config, Configurables, Configuration, configurationChange, dependency, Device Configuration, Dir, display property, enabledSdks, gradle, logging.enabled, method-level config, minsdk, multiple display, offline, onPostResume, package-level config, qualifiers, repo.id, repo.url, resourceDir, Robolectirc.buildActivity, Robolectric, robolectric example, robolectric Hello World, robolectric.properties, robolectric.properties location, RobolectricTestRunner, RuntimeEnvironment, save instance restore, SDK, setQualifiers, setup, ShadowActivity, ShadowDisplay, ShadowDisplayManager, shadowOf, system properties, targetsdkversion, tdd, unit test framework, Visible, [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/





반응형

댓글