brunch

You can make anything
by writing

C.S.Lewis

by 이승현 Jun 12. 2017

MVP unit test

Junit & Mockito

Unit test


컴퓨터 프로그래밍에서 소스 코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차다.


단위 테스트를 적용하기 위해서는 러닝 커브도 필요하고, 무엇보다도 직접적인 기능 개발 이외에도 공수가 들어가기 때문에 꺼리는 부분이 있는데 단위 테스트를 하면 아래와 같이 여러 장점이 있습니다.


잘못된 부분을 빠르게 확인할 수 있게 해준다. >> 프로그램의 안정성이 높아진다.

디버깅 시간을 단축시켜준다. >> 개발 시간을 줄여준다.

모듈이 의도대로 동작하고 있음을 확인할 수 있다. >> 리펙토링 시 부담을 줄여준다.

프로그램의 각 부분을 검증한다. >> 유닛 자체의 불확실성을 제거해준다 >> 유닛을 합쳐서 다시 검증하는 통합 테스트에 유용하다.





Android Test


안드로이드 테스트는 JUnit을 기반으로, JVM에서 local unit test를 하거나 안드로이드 기기에서 instrumented test를 할 수 있습니다.


#01 Test types


Local unit tests

컴퓨터의 로컬 JVM(Java Virtual Machine)에서 실행되는 테스트.

안드로이드 프레임워크 종속성이 없거나 모의 객체를 생성할 수 있는 경우 이 테스트 사용.

module-name/src/test/java/.


Instrumented tests

안드로이드 기기나 에뮬레이터에서 실행되는 테스트.

사용자 상호작용을 자동화하는 통합 및 기능적 UI 테스트를 작성하거나 테스트에 모의 객체가 충족할 수 없는 Android 종속성이 있는 경우 이 테스트를 사용.

module-name/src/androidTest/java/.


#02 Test directories


모든 안드로이드 테스트에 대해 다루진 않고, JUnitMockito를 이용한 MVP Unit test에 대해 다뤄보겠습니다.




JUnit & Mockito



JUnit은 Java 프로그래밍 언어 용으로 설계된
단위 테스트 프레임워크입니다.


Mockito는 자바에서 단위 테스트를 하기 위해
Mock을 만들어주는 프레임워크입니다.


모의 객체(Mock Object)란 주로 객체 지향 프로그래밍으로 개발한 프로그램을 테스트할 경우 테스트를 수행할 모듈과 연결되는 외부의 다른 서비스나 모듈들을 실제 사용하는 모듈을 사용하지 않고 실제의 모듈을 "흉내"내는 "가짜" 모듈을 작성하여 테스트의 효용성을 높이는 데 사용하는 객체이다.




Create a Local Unit Test


#01 build.gradle


build.gradle에 아래와 같이 dependencies를 추가합니다.

dependencies {
    ...
    // JUnit 4 framework
    testCompile 'junit:junit:4.12'
    // Mockito framework
    testCompile 'org.mockito:mockito-core:1.10.19'
    ...
}




#02 New Test java class


"module-name/src/test/java/." 디렉터리에 Test java class를 생성합니다.

Model-Presenter-View에서 UI test가 아닌 Presenter의 Unit test만 하기 위해서 "PresenterTest.java"을 생성합니다.





#03 @Before


가장 먼저 해야 할 일은 @Before annotation 메서드를 작성하는 것입니다.

보통 테스트를 하기 전에 필요한 객체를 초기화하거나 기타 설정들을 위해 이용합니다.

@Before 메서드는 모든 테스트 전에 실행되기 때문에 만약 4개의 테스트가 있는 경우 4번 실행됩니다.

즉, 이전 @Before 메서드의 영향을 받지 않습니다.


#03 4 times @Before


PresenterTest에서는 Model, View, Presenter 인스턴스 변수를 생성하기 위해 @Before를 이용합니다.

@Before
public void setup() {
    ...
}


이때 실제 Model과 View 인스턴스 변수를 생성하기에 공수도 많이 들어가지만 안드로이드 프레임워크의 영향을 받기 때문에 Mockito.mock() API를 이용해 mock Model과 mock View를 생성합니다.

    LoginActivityContract.Model mockLoginModel;
    LoginActivityContract.View mockView;

    @Before
    public void setup() {
        mockLoginModel = Mockito.mock(LoginActivityContract.Model.class);
        mockView = Mockito.mock(LoginActivityContract.View.class);
    }


그리고 이를 기반으로 Presenter 인스턴스 변수도 생성합니다.

@Before 메서드에서 정의한 모의 객체들을 이용해 테스트 및 상호 작용에 적합한 동작을 정의할 수 있습니다.

import org.junit.Before;
import org.mockito.Mockito;

import shlee.mvpexapmle.login.LoginActivityContract;
import shlee.mvpexapmle.login.LoginActivityPresenter;
import shlee.mvpexapmle.login.User;

public class PresenterTest {

    LoginActivityContract.Model mockLoginModel;
    LoginActivityContract.View mockView;
    LoginActivityPresenter presenter;
    User user;

    @Before
    public void setup() {
        mockLoginModel = Mockito.mock(LoginActivityContract.Model.class);

        mockView = Mockito.mock(LoginActivityContract.View.class);

        presenter = new LoginActivityPresenter(mockLoginModel);
        presenter.setView(mockView);

        user = new User("sh", "lee");
    }
public interface LoginActivityContract {

    interface View {
        String getFirstName();
        String getLastName();

        void showUserNotAvailable();
        void showInputError();
        void showUserSaveMessage();

        void setFirstName(String firstName);
        void setLastName(String lastName);
    }

    interface Presenter {
        void setView(LoginActivityContract.View view);
        void loginButtonClicked();
        void getCurrentUser();
        void saveUser();
    }

    interface Model {
        void createUser(String firstName, String lastName);
        User getUser();
    }

}




#04 @Test


@Test annotation을 이용하여 테스트할 메서드를 작성합니다.

@Test가 선언되면 이 메서드는 테스트 대상임을 의미합니다.

아래와 같이 Presenter의 핵심 로직만 테스트하는데 집중하면 됩니다.


#04 MVP pattern logic


먼저 "loadtheUserFromTheRepository_whenValidUserIsPresent" @Test 메서드를 생성합니다.

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


TDD 방식의 Unit 테스트가 아닌, View의 이벤트 호출(2. Invoke event)에 따른 Presenter의 동작(3. Request data / 5.Update view)을 테스트하기 때문에 BDD 방식의 Behavior driven test naming convention을 이용해 메서드 명을 지었습니다.


그다음 테스트하고 싶은 코드를 작성하게 됩니다.

특성 동작에 대해 Presenter가 view와 model과 상호작용을 잘하는지를 아래와 같이 테스트할 수 있습니다.

@Test
public void loadtheUserFromTheRepository_whenValidUserIsPresent() {
    Mockito.when(mockLoginModel.getUser()).thenReturn(user);

    presenter.getCurrentUser();

    // verify model interactions
    Mockito.verify(mockLoginModel, Mockito.times(1)).getUser();

    // verify view interactions
    Mockito.verify(mockView, Mockito.times(1)).setFirstName("sh");
    Mockito.verify(mockView, Mockito.times(1)).setLastName("lee");
    Mockito.verify(mockView, Mockito.never()).showUserNotAvailable();
}


구체적인 코드 작성법은 아래 링크를 참고해 주시기 바랍니다.




Run Test


테스트를 실행하려면 다음 단계를 진행해야 합니다.


1. 안드로이드 스튜디오의 "Run > Edit Configurations"를 선택합니다.

#05 Edit Run/Debug Configurations


2. "Run/Debug Configurations" 대화 상자에서 "Android JUnit"을 추가합니다.

#06 Add Android JUnit


3. 이름, 테스트 종류, 모듈 등 여러 옵션을 선택하고 "Apply" 버튼을 선택합니다.

#07 Apply Test options


4. "Run" 버튼을 선택 해 테스트를 실행합니다. 

#08 Run Test




Test result


테스트 결과 다음과 같이 볼 수 있습니다.

모든 테스트 성공 시 프로그래스바가 녹색으로 표시되고, 실패 시 빨간색으로 표시됩니다.


#09 Test Result - Passed
#10 Test Result - Failed


테스트 결과를 안드로이드 스튜디오를 통해 다른 형태로 추출(Export)할 수도 있습니다.


#11 Export Test Results
#12 Test results by html







앱의 주요 동작 로직을 테스트 코드로 작성하기에 MVP 패턴이 수월하네요.

MVC 패턴의 Controller는 안드로이드 프레임워크에 의존성이 강해서 Mock 객체를 작성하기에 까다로웠는데 MVP 패턴의 Presenter는 그에 따른 문제가 없네요.

작가의 이전글 MVP pattern

작품 선택

키워드 선택 0 / 3 0

댓글여부

afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari