반응형

Robolectric 은 4.0 이후 버전부터는 안드로이드 공식 테스트 라이브러리와 완벽하게 호환되도록 설계되었습니다.

AndroidX Test API 를 사용하면 Robolectric test 과 Instrumentation test 모두 공통으로 적용 가능하기 때문에 개발자에게 학습 부하를 줄여 줍니다.

 

TestRunner

TestRunner 는 테스트 패키지를 기기(또는 JVM)에 로드하고, 테스트를 실행하고, 테스트 결과를 리포팅하는 역할을 합니다.

Robolectric Test 에서는 자체적으로 RobolectricTestRunner 를 제공하고 있지만, AndroidX Test API 에 포함되어 있는 AndroidJUnit4 클래스와도 호환됩니다.

 

Robolectric

@RunWith(RobolectricTestRunner.class)
public class SandwichTest {
}

 

AndroidX Test

import androidx.test.ext.junit.runners.AndroidJUnit4;

@RunWith(AndroidJUnit4.class)
public class SandwichTest {
}

참고로 AndroidJUnit4 클래스는 androidx.test.ext 패키지에 있기에 이 모듈을 build.gradle 에 추가 해줘야 합니다. (androidx.test.runner 패키지에 있는 AndroidJUnit4 클래스는 deprecated 되었습니다.)

 

Application

대부분의 안드로이드 코드는 Context 중심으로 구성되어 있기 때문에, 대다수의 테스트에서 Application context 를 들고 있는 경우가 많습니다.

 

AndroidX Test

import androidx.test.core.app.ApplicationProvider;

@Before
public void setUp() {
  Context context = ApplicationProvider.getApplicationContext();
}

Robolectric 문서에 나와있는 RuntimeEnvironment.application 은 deprecated 되었습니다.

 

☞ android api 29 version issue

안드로이드 API 29 버전에서 테스트 하기 위해서는 Java 9 버전 이상을 요구합니다. 그러나 Android Studio 3.5.3 버전까지만 해도 아직까지 JDK 9 을 지원하고 있지 않기 때문에 targetSdkVersion 이 29 인 경우,

  1. Edit Configuration 에서 Test run Configuration 만 JDK 9 로 바꿔주거나
  2. @Config 어노테이션을 사용하여 테스트 환경을 API Version 29 미만으로 설정하는 방식으로 조치해야 합니다.

 

Activity

RobolectricRobolectric.setupActivity() 를 제공합니다. 이는 사용자와 상호작용할 준비를 마친 Resumed 상태의 액티비티를 설정해줍니다.

 

또한 Robolectric.buildActivity() 도 있는데, 이는 ActivityController 를 리턴하여 개발자가 Activity Lifecycle 을 컨트롤 할 수 있게 해줍니다. 이를 잘 다루기 위해서는 Lifecycle 에 대한 정확한 이해가 뒷받침 되어야 하겠습니다. 잘못된 상태에서 Activity 를 사용하는 것은 의도치 않은 행동을 야기하거나 다른 테스트에 호환성 문제를 일으킬 수 있습니다.

 

AndroidX Test API 에서 제공하는 ActivityScenario 는 앞서 말한 두가지를 대체할 수 있습니다. ActivityScenario 는 Lifecycle 전환에 대해 보다 엄격한 기준을 두고 몇몇 상황에 대해 제한합니다. Instrumentation test 에서는 @Rule 어노테이션에 사용할 수 있는 ActivityScenarioRule 클래스도 있습니다.

 

Robolectric

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
    @Test
    public void testIntent() {
        // given
        Intent intent = new Intent();
        ActivityController controller = Robolectric
                                          .buildActivity(MainActivity.class, intent)
                                          .setup();

        // when
        controller.pause().stop();

        // then
        assertEquals(controller.getIntent(), intent);
    }
}

 

AndroidX Test

@RunWith(AndroidJUnit4.class)
public class ExampleUnitTest {
    private Context context;

    @Before
    public void setUp() {
        context = ApplicationProvider.getApplicationContext();
    }

    @Test
    public void testScenario() {
        // given
        Intent intent = new Intent(context, MainActivity.class);
        ActivityScenario scenario = ActivityScenario.launch(intent);

        // when
        scenario.moveToState(Lifecycle.State.CREATED);

        // then
        scenario.onActivity( activity ->
                assertEquals(activity.getIntent(), intent) );
        }
}

기본적으로 Rebolectric 에서 하는 테스트는 UI Thread 이기 때문에 UI 처리를 위한 별도의 동기화를 하지 않아도 됩니다만, ActivityScenario.onActivity() 의 경우 보다 안전하게 액티비티에 접근할 수 있도록 해줍니다.

 

Views

Robolectric 은 View 와 상호작용 하는 데 매우 제한된 API 를 제공합니다.

 

대표적으로 Activity.findViewById() 같은 메소드 정도는 사용할 수 있습니다.

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
    @Test
    public void testFindViewById() {
        // given
        ActivityScenario scenario = ActivityScenario.launch(MainActivity.class);
        String helloText = "Hello, Robolectric!";

        // when
        scenario.moveToState(Lifecycle.State.CREATED);

        // then
        scenario.onActivity(activity -> {
            TextView textView = activity.findViewById(R.id.hello_text);
            textView.setText(helloText);
            assertEquals(textView.getText().toString(), helloText);
        });
    }
}

Robolectric 4.0 버전부터는 instrumentation test 에서 View 를 탐색하고 상호작용 하는 API 를 제공해주는 Espresso 를 지원합니다.

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
        @Test
    public void testWithEspresso() {
        // given
        ActivityScenario scenario = ActivityScenario.launch(MainActivity.class);

        // when
        scenario.recreate();

        // then
                // espresso
        onView(withId(R.id.hello_text)).check(matches(withText("Hello World!")));
    }
}

 

Fragments

AndroidX Test API 에서는 FragmentScenario 를 통해 안전하게 테스트할 수 있도록 지원 해줍니다.

 

FragmentScenario 를 사용하기 위해서는 androdx.fragment:fragment-testing 모듈을 앱 수준의 build.gradle 에 의존성을 추가해줘야 합니다.

dependencies {
    testImplementation 'androidx.fragment:fragment-testing:1.2.2'
}

Fragment 를 테스트 하는 것은 어떤 Fragment 냐에 따라 나뉩니다.

  • UI가 포함된 Graphical Fragment
    • 이 경우에는 launchFragmentInContainer() 메소드를 호출해야 합니다.
      그래야 FragmentScenario 가 Activity 의 Root View 에 attach 합니다.
  • UI가 없는 Non-graphical fragment
    • 이 경우에는 launchFragment() 메소드를 호출해야 합니다.
      그러면 FragmentScenario 가 Root View 를 갖지 않은 Activity 에 attach 합니다.

프래그먼트를 launching 하고 나면 FragmentScenarioRESUMED 상태를 갖습니다.

그리고 이 상태는 Graphical Fragment 의 경우 사용자와 상호작용 할 수 있는 UI 가 그려진 상태이기 때문에 Espresso UI Test 를 수행할 수도 있습니다.

 

다음은 launchFragmentInContainer() 를 사용한 테스트 예제입니다.

@RunWith(AndroidJUnit4::class)
public class GraphicalFragmentTest {
    @Test
        public void testEventFragment() {
        Bundle fragmentArgs = new Bundle();
        fragmentArgs.putInt("selectedListItem", 0);

        FragmentFactory factory = new FragmentFactory();
        FragmentScenario scenario = launchFragmentInContainer<MyFragment>(fragmentArgs, factory);
        onView(withId(R.id.text)).check(matches(withText("Hello World!")));
    }
}

Robolectrics 에서 제공하던 SupportFragmentUtilSupportFragmentControllerAndroid P 부터 deprecated 되었습니다.


따라서 프래그먼트에 대한 테스트는 androidx 패키지에서 제공하는 FragmentScenario 를 통해 진행하면 되겠습니다.

 

 

반응형
  • jeon 2020.05.27 09:23

    Espresso 학습중에 Robolectric을 같이 보고 있는데요
    왠지 에뮬레이터나 단말이 필요한가 아닌가의 차이가 있고 Espresso가 Robolectric을 완전 대체 가능해보입니다.
    테스트 피라미드에서는 Espresso는 최상위, Robolectric 은 중간에 포지션되어있으나, 돌려보면 비슷한 시간이 걸리는거 같기도 하고요
    그래서 Robolectric은 그냥 pass하고 Espresso만 학습/적용할까 하는데 Ready Kim님의 의견이 궁금합니다.

    • Favicon of https://readystory.tistory.com BlogIcon Dev. Ready Kim 2020.05.27 19:37 신고

      안녕하세요? 방문해주셔서 감사합니다.
      제 의견을 말씀 드리자면, Robolectric은 Espresso와 포지셔닝이 조금 다른데요.
      먼저 안드로이드 프로젝트에서 Unit Test와 Android Test의 패키지가 나뉘어 있다는 것과 왜 나뉘었는지를 알면 이해하기가 편합니다.

      Unit Test 패키지에서 실행되는 테스트들은 실제 안드로이드 기기(에뮬레이터 포함)에서 동작하는 것이 아니기 때문에 ART(또는 달빅 머신)이 아닌 JVM에 로드되어 동작하는데요. 그러다보니 안드로이드 패키지에 대한 자원(Activity, View 등)에 대한 사용이 불가능합니다.

      이러한 이유로 유닛 테스트를 하는데 상당히 많은 제한사항을 마주하게 되는데요. 이를 해결해주는 것이 Robolectric 입니다. Robolectric은 안드로이드 패키지를 가상으로 구현하여 Unit Test 에서도 안드로이드 자원에 대한 접근이 가능한 것처럼 하게 해줍니다.

      부연 설명이 길어졌지만 정리하자면, Espresso는 Unit Test가 아닌 Android Test(UI Test)에 사용되는 테스트 프레임워크이고, Robolectric은 Android Test에서도 Espresso와 함께 사용 가능하긴 하지만 주로 Unit Test에서 안드로이드 자원 사용 불가라는 한계를 극복하여 원할한 유닛 테스트를 하게 해주는 프레임워크라 보시면 되겠습니다.

반응형