전에 간단한 샘플 코드를 만들어서 Espresso를 통해 Unit Test를 해보았는데,
이번에는 Multi-Module 구조의 샘플 코드에서 Unit Test 를 해보았다.
역시 테스트하는 환경이 조금(?) 변경됐다고 빌드가 정상적으로 되지 않았다.
기본 사용법은 이전 게시글에서 작성했으니, 이번에는 발생한 문제와 해결 방법에 대하여 작성해볼 생각이다.
사용 방법은 다음 게시글에서 확인하면 될 것이다.
우선,
멀티 모듈 구조에서 테스트를 진행할 모듈의 gradle에 Unit Test에 필요한 라이브러리를 추가해준다.
필자는 Presentation 모듈에 해당 라이브러리를 추가해주었다.
Presentation 모듈이 모든 Activity 에 대한 참조를 받고있고, launcher Activity 가 있기 때문에 해당 모듈에서 테스트를 진행하도록 하였다.
Espresso를 사용한 테스트이기 때문에 계측 테스트로 진행한다.
InstrumentedTest 클래스에서 테스트 코드를 작성해 주었다.
해당 테스트 코드에서 이전에 사용하지 않았던 함수를 사용하였다.
Espresso.pressBack()
해당 함수는 하드웨어 백 버튼을 누르는 동작을 수행해 준다.
CountDownLatch(1).await(5, TimeUnit.SECONDS)
해당 함수는 일정 시간동안Delay를 주는 함수이다.
보통 스레드를 여러 개 사용할 때, 다른 스레드에서 실행되는 특정 수의 작업이 완료될 때까지 대기하기 위해서 사용하는 함수이지만,
Thread.sleep() 함수 대신하여 한번 사용해 보았다.
CountDownLatch(count).await(timeOut, TimeUnit)
형태로 사용하며, TimeUnit 으로 지정한 기준으로 timeOut이 되거나, count 가 모두 줄어들 때까지 delay를 준다고 생각하면 된다.
필자는 다중 스레드 환경에서 작업한 것이 아니기 때문에, count를 1로 두고 timeOut이 될 때까지 delay를 주기 위해 사용했다.
즉,
CountDownLatch(1).await(5, TimeUnit.SECONDS) == Thread.sleep(500L)
이렇게 변경해도 문제는 없다.
(처음 봐서 그냥 사용해보고 싶었다.)
필자가 이 부분에delay를 준 이유는 동기화를 위해서이다.
갱신되는 이벤트가 끝난 후에, 해당 화면에 대한 이벤트를 입력하지 않으면 에러가 발생하기 때문이다.
api를 통해 데이터를 가져와서 뿌려주는 동작이기 때문에, 일정 시간 딜레이 없이 바로 아이템 클릭 이벤트와 같은 것들을 추가하면 안 된다.
코드의 주석을 보면 알겠지만,
Espresso를 사용하여 이벤트를 준 아이템들은 하나의 모듈이 아니다.
클릭 이벤트를 통해 화면을 이동하고 그곳에서 다른 이벤트를 입력하였는데, 이것들은 하나의 모듈이 아니라 다른 모듈에 있는 화면들이다.
이것이 다른 모듈들을 참조하는 Presentation 모듈에서 테스트 코드를 작성한 이유이다.
전체 화면을 이동하면서 테스트를 진행하고 싶은데, 참조하고 있지 않은 모듈이 있으면 해당 모듈에서 테스트가 불가능하다.
따라서, Coordinator Pattern을 사용하여 모든 화면의 참조를 받고 있으며, 이동이 가능한 Presentation 모듈에서 테스트 코드를 작성하였다.
참조를 받고 있기 때문에 별다른 처리 없이 해당 액티비티의 리소스 ID를 가져올 수 있고 그것을 통하여 이벤트를 주어 테스트를 진행할 수 있다.
물론, 해당 리소스 ID를 가져오지만 UI 상에 해당 리소스가 존재하지 않으면 오류가 발생하게 된다.
해당 리소스 ID 를 호출하면 자동으로 이동하는 것이 아니라, 보이고 있는 액티비티와 동일한 액티비티에 있는 리소스를 사용해야 한다.
참조하지 않은 모듈에서의 테스트는 해당 모듈에 직접 테스트 코드를 작성하고, 해당 모듈의 액티비티를 런처로 만들어서 접근하도록 한 후 테스트를 진행하는 등 별도의 작업을 추가해주어야 한다.
다음은,
RecyclerView의 Item에 이벤트를 주는 작업을 해보았다.
Android Developer에서 검색을 해보면 다음과 같이 나온다.
contrib 패키지를 사용하면 된다고 한다.
Gradle에 추가해주었다.
해당 함수들을 사용하여 이벤트를 주면 된다고 한다.
이처럼 테스트 코드를 만들어 보았다.
우선 맨 처음에 1초 딜레이를 준 이유는, VM을 사용하여 테스트를 진행하는데 간헐적으로 이 녀석이 느려지면서 클릭 이벤트에서 에러를 발생시키고 죽는 경우가 발생했다.
따라서 이를 방지하기 위해 1초의 딜레이를 추가해 주었다.
Espresso.onView(withId(R.id.rv_movies))
.perform(RecyclerViewActions.scrollToPosition<MovieAdapter.ViewHolder>(5))
해당 함수는 해당 포지션으로 scroll을 시키는 함수이다.
id 값은recyclerView의 id 값을 넣어주고, <> 안에는 해당 recyclerView에서 사용하는ViewHolder를 넣어주어야 한다.
developer나 다른 예제를 확인해봤을 때<>를 사용해 ViewHolder를 넣어주지 않고 사용하는 것을 볼 수 있는데, 최근에 바뀌었는지 몰라도 반드시 ViewHolder를 넣어주어야 한다.
Espresso.onView(withId(R.id.rv_movies))
.perform(RecyclerViewActions.actionOnItemAtPosition<MovieAdapter.ViewHolder>(8, click()))
해당 함수는 해당 포지션의 item에 이벤트를 주는 것이다.
click()을 사용하여 8번째 position의 item을 클릭해주는 이벤트를 주었다.
이처럼,
RecyclerViewActions
를 선언하여 자동 완성되는 함수에서 recyclerView에서 사용 가능한 함수를 확인하고 원하는 함수를 사용해 테스트를 진행하면 된다.
이제 테스트 코드를 완성했으니 빌드를 해보자.
안된다.
Duplicate class Error 가 발생한다.
추가한 라이브러리에서 동일하게 추가되는 class가 있는 것으로 보인다.
뭐 하나 추가하면 하나씩 에러가 발생한다.
에러의 첫 번째 줄인
Duplicate class org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey
를 토대로
열씸히 구글링 해서 해결 방법 2개를 찾았다.
1. Truth를 제거한다.
2. 해당 class를 exclude 한다.
우선,
Truth 를 사용하지 않는다면 Implement 된 Truth 라이브러리를 제거하고 빌드하면 정상적으로 동작한다.
하지만, 지금은 Espresso 만 사용하였지만 실제 Unit Test를 진행할 때Truth를 사용하는 경우가 많을 것이라고 생각했다.
따라서, Duplicate 되는 class를 exclude 시켜주는 작업을 진행하였다.
contrib를 추가하였을 때 해당 에러가 발생하였으니, 해당 라이브러리에서 exclude 하는 방법을 찾아보았다.
이처럼 에러가 발생하는 클래스 자체를 exclude 시켜줘서 duplicate를 해결하였다.
이처럼 gradle 설정이 끝나고 다시 빌드를 해보면, 정상적으로 테스트가 진행되는 것을 확인할 수 있다.
간단하게 다른 모듈에 있는 화면을 이동시키도록 Unit Test를 진행해 보았다.
VM을 사용하고, Mock 객체를 사용해 가 데이터를 넣는 테스트를 하게 되면 테스트 자체가 상당히 복잡해질 것으로 보인다.
또한, presentation 모듈에서 모든 모듈을 참조하고 있기 때문에 문제없이 간단히 테스트를 진행했지만,
참조하지 않는 모듈이 있을 때는 Unit Test를 진행하기 불편할 것으로 보인다.
따로 모듈을 떼어내거나 런처로 설정하고, 정상적인 동작을 하기 위해 필요한 데이터를 넣어주는 등. 일련의 작업들을 추가하고 테스트가 진행되어야 한다는 게.. 나중에 한 번쯤 도전해봐야 할 것 같다.
해당 게시글에 사용한 예제는 GitHub 에 올려두었다.
https://github.com/HeeGyeong/ModuleArchitecture
'Android > TDD' 카테고리의 다른 글
[Mockito] Spy Object를 사용하여 Unit Test를 해보자. (0) | 2022.04.25 |
---|---|
[TDD] TDD 란 ? (0) | 2022.02.27 |
[Mockito] Mockito를 사용해 Instrumented Unit Test를 해보자 (0) | 2022.02.25 |
[Mockito] Mockito를 사용하여 Unit Test 를 해보자 (feat. Truth + Junit4) (2) | 2022.02.24 |
[Truth] Truth를 사용하여 Unit Test 를 해보자 (feat. Junit4) (0) | 2022.02.23 |