안드로이드 테스트

이 문서는 안드로이드 테스트 방법과 가이드라인을 제공한다. 이는 안드로이드 테스트 추천 라이브러리와 활용 방법에 대한 내용을 포함한다. 단 아래 내용은 추천 사항일 뿐이며, 각 프로젝트에 따라서 언제든 자유롭게 다른 방법을 적용하거나 제안할 수 있다. 다만 이 경우에도 프로젝트 내에서는 통일성을 갖는 것을 장려한다.

테스트 프레임워크 및 라이브러리

안드로이드 테스트를 위해 아래 라이브러리들을 활용한다. 추가 라이브러리 활용은 언제든지 가능하나, 스태틱(static) 함수 모킹 등 테스트 가이드라인을 벗어나는 기능을 활용하기 위해 더 강력한 라이브러리(e.g PowerMock)를 사용하는 것은 지양한다.

JUnit 4

https://junit.org/junit4/

안드로이드 테스트를 위한 프레임워크이다. JUnit 5가 출시되어 있지만 안드로이드 환경에서 사용하려면 추가적인 작업이 필요하여 아직 기본적으로 제공되는 JUnit 4를 사용하고 있다.

Mockito 2

http://site.mockito.org/

모킹을 위한 라이브러리이다. 오브젝트를 모킹하여 테스트 오브젝트가 의존하고 있는 오브젝트를 모킹하여 테스트 환경을 구성할 수 있다. 스태틱 함수나 파이널 클래스(final class) 등은 모킹할 수 없다. 이를 가능하게 하는 파워목(PowerMock) 라이브러리가 있지만, 일반적으로 스태틱 함수의 모킹이 반드시 필요한 경우 코드 구성이 잘못되어 있거나 테스트 방법이 잘못되었을 가능성이 높기에 파워목 사용을 지양한다.

Robolectric

http://robolectric.org/

안드로이드 프레임워크의 동작을 시뮬레이션하여 안드로이드 액티비티나 뷰 등의 동작이 필요한 테스트를 안드로이드 에뮬레이터나 실제 디바이스 없이 수행 가능하게 도와준다. 유닛 테스트 시 안드로이드 프레임워크를 활용해야만 하는 경우 유용하다. Robolectric 4 이 출시되었지만 아래 문서는 Robolectric 3 기준으로 작성되어 있다.

유닛 테스트

유닛 테스트 기본 가이드라인은 테스트 기본 원칙 문서를 참고한다.

실행 방법

gradle 콘솔

gradlew 를 사용하여 테스트한다. 이 때 테스트 메소드를 지정함으로써 원하는 테스트만 수행할 수 있다.

# 특정 테스트 실행
./gradlew test --tests com.buzzvil.locker.SomeTest.someSpecificFeature
# 특정 패키지 테스트 실행
./gradlew test --tests all.in.specific.package*

안드로이드 스튜디오

실행하고자 하는 테스트 함수 혹은 클래스에 커서를 둔 뒤 컨트롤 + 시프트 + D 키 조합을 사용해 테스트를 수행할 수 있다. 또한 테스트 메소드 좌측에 보이는 아이콘을 눌러서 테스트를 수행할 수 있다.

icon-test

테스트 코드 구성

네이밍 가이드라인

네이밍은 Javatests style rules 를 따른다. 테스트 대상 메소드 이름 앞에 test 를 붙여서 카멜 표기법으로 표현한다. 테스트 상황에 대한 표현은 언더바(_)를 사용해 구분한 뒤 카멜 표기법을 활용해 표현한다.

@Test
public void testIsCampaignFilteredOut_noFilter() {
    ...
}

임포트(import) 가이드라인

테스팅 메소드는 스태틱 임포트(static import)를 사용해 임포트한다.

// 임포트 이후 `Mockito.mock` 대신 `mock` 을 바로 사용한다.
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

디펜던시 모킹(mocking) 가이드라인

디펜던시 인젝션(Depedency Injection)을 사용해서 디펜던시를 모킹한다.

class CampaignFilter {
    CampaignFilter() {
        this(PreferenceHelper.getString(PrefKey.FILTERED_AD, "").split(DELIMITER),
                PreferenceHelper.getString(PrefKey.FILTERED_CONTENT, "").split(DELIMITER));
    }

    CampaignFilter(final String[] filteredAds, final String[] filteredContent) {
        this.filteredAds = filteredAds;
        this.filteredContent = filteredContent;
    }

    // ...
}
class CampaignFilterTest {
    @Test
    public void testIsCampaignFilteredOut_noFilter() {
        // 필터가 없는 경우를 테스트한다.
        final CampaignFilter campaignFilter = new CampaignFilter(new String[]{}, new String[]{});
        ...
    }
}

안드로이드 프레임워크 모킹

액티비티 생애 주기(Activity lifecycle)

로보렉트릭(Robolectric)을 사용해서 액티비티 생애 주기를 테스트한다.

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class)
public class VideoOverlayActivityTest {
    @Test
    public void testOnCreate() {
        final VideoOverlayActivity activity = Robolectric.buildActivity(
            VideoOverlayActivity.class, getDefaultIntent()).create().get();

        assertThat(activity.isFinishing(), is(false));
    }
}

쉐도잉(Shadowing)

스태틱 함수를 모킹하는 것은 가능한 지양한다. 싱글톤의 활용 역시 클래스 안에서 직접 생성해서 사용하기 보다는 디펜던시 인젝션을 사용해서 디펜던시를 가져야 한다. 다만 기존의 코드로 인해 불가피할 경우 로보레트릭의 쉐도우를 사용하여 구현한다.

@Implements(SomeSingleton.class)
public class ShadowSomeSingleton {
    static SomeSingleton someSingleton;

    @Implementation
    public static SomeSingleton getInstance() {
        if (someSingleton == null) {
            someSingleton = mock(SomeSingleton.class);
        }

        return someSingleton;
    }
}
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, shadows = ShadowSomeSingleton.class)
public class SomeActivityTest {
    // ...
}

References