Study Record

[안드로이드] 계측 테스트 참고 본문

안드로이드

[안드로이드] 계측 테스트 참고

초코초코초코 2023. 7. 18. 21:00
728x90

 

😶 RecyclerView 테스트

 

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText

onView(withId(R.id.recycler_view)).perform(
   RecyclerViewActions
       .scrollToPosition<RecyclerView.ViewHolder>(9)
)

onView(withId(R.id.recycler_view)).perform(
   RecyclerViewActions
       .scrollTo<RecyclerView.ViewHolder>(
           withText("happy")
       )
)

scrollToPosition(9) 으로 지정된 위치로 스크롤한다. RecyclerView 에 사용되는 목록의 길이가 정적이지 않은 경우 scrollTo() 함수와 withText() 를 사용해서 특정 텍스트가 들어간 항목으로 스크롤할 수 있다.

 

gradle 종속 필요

implementation 'androidx.test.espresso:espresso-contrib:3.5.1'
 

RecyclerViewActions  |  Android Developers

androidx.constraintlayout.core.motion.parse

developer.android.com

 

 

😶 check() 

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText

onView(withText("happy"))
    .check(matches(isDisplayed()))

check() 로 true 인지 아닌지 확인할 수 있다. 특정 텍스트가 들어간 View 가 화면에 있는지 아닌지 확인할 수 있다.

 

 

😶 탐색 구성요소 테스트 작성하기

 

dependencies 추가하기

// Test Dependencies
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.4.0'
androidTestImplementation 'androidx.navigation:navigation-testing:2.5.2'

debugImplementation 'androidx.fragment:fragment-testing:1.5.3'

 

탐색 구성요소 테스트하기

탐색 구성요소를 테스트할 때는 기기나 에뮬레이터가 눈에 보이게 이동하지 않는다. 테스트하는건 navigation controller 가 올바른 목적지에 도착하도록 하는지를 확인한다.

 

navController 객체 선언하기

val navController = TestNavHostController(
   ApplicationProvider.getApplicationContext()
)

 

 

launchFragmentInContainer() 는 탐색하고 있는 액티비티나 다른 프래그먼트를 인식하지 못하기 때문에 실제로 탐색은 불가능하다. (실제 테스트 기기에서 탐색을 볼 수 없다.)

 

val letterListScenario = launchFragmentInContainer<LetterListFragment>(themeResId = R.style.Theme_Words)

letterListScenario.onFragment {fragment ->
    navController.setGraph(R.navigation.nav_graph)

    Navigation.setViewNavController(fragment.requireView(), navController)
}

onFragment 함수로 실제로 프래그먼트가 시작할 때 이벤트를 가져와 Nav Controller 의 시작 프레그먼트를 명시해준다.

 

초기 프래그먼트로 예시에선 LetterListFragment 를 명시했는데 이 프래그먼트의 특정 버튼을 클릭한다던지 RecyclerView 를 클릭한다던지 등의 작업을 실행하면 다른 프래그먼트(WordListFragment)로 탐색을 진행한다고 하면, assertEquls() 함수로 현재 Nav Controller 의 프래그먼트가 다른 프래그먼트(WordListFragment)인지 확인하는 것으로 탐색 테스트 과정을 진행할 수 있다.

// LetterListFragment 의 리스트 목록을 클릭하면 WordListFragment 로 탐색한다.
onView(withId(R.id.list_recycler_view))
    .perform(
        RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(2, click())
    )

// 현재 Nav Controller 에 있는 프래그먼트가 WordListFragment 인지 확인한다.
// R.id.wordListFragment 는 NavGraph(xml)에 정의되어 있다.
assertEquals(navController.currentDestination?.id, R.id.wordListFragment)

 

 

😶 계측 테스트 동시에 진행하기

만약 탐색 과정 테스트 중 시작 프래그먼트에서 버튼 10개 당 탐색하는 프래그먼트가 다르다고 가정하면, 테스트 코드를 버튼 10개를 클릭하는 고정을 10번 반복해야할 것이다. 이렇게 같은 환경에서 반복되는 코드를 작성하지 않고 클래스에 대해 모든 동일한 환경을 설정할 수 있다.

 

바로 @Before 어노테이션으로 가능하다.

class NavigationTests {

    private lateinit var letterListScenario: FragmentScenario<LetterListFragment>
    private lateinit var navController: NavController

    @Before
    fun setUp() {
        navController = TestNavHostController(
            ApplicationProvider.getApplicationContext()
        )

        letterListScenario = launchFragmentInContainer(themeResId = R.style.Theme_Words)

        letterListScenario.onFragment {fragment ->
            navController.setGraph(R.navigation.nav_graph)

            Navigation.setViewNavController(fragment.requireView(), navController)
        }
    }

    @Test
    fun navigate_to_words_nav_component() {
        onView(withId(R.id.list_recycler_view))
            .perform(
                RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(2, click())
            )

        assertEquals(navController.currentDestination?.id, R.id.wordListFragment)
    }

    @Test
    fun navigate_to_blank_nav_component() {
        onView(withId(R.id.letter_black_btn)).perform(click())

        assertEquals(navController.currentDestination?.id, R.id.blankFragment)
    }
}

이 예시는 LetterListFragment 에서 RecyclerView 의 목록을 클릭하면 WordListFragment 로 탐색하고 버튼을 클릭하면 BlankFragment 로 넘어가는 탐색 과정을 테스트한다. 먼저 @Before 어노테이션을 달고 있는 함수가 실행되면서 Nav Controller 와 초기 프레그먼트를 LetterListFragment 로 명시한다. 그 뒤, navigate_to_words_nav_component() 와 navigate_to_blank_nav_component() 가 실행되면서 @Before 함수가 실행된 후의 같은 환경에서 테스트 작업을 할 수 있다.

 

@Before 어노테이션 말고 유용한 어노테이션이 있는데 바로 @After, @AfterClass, @BeforeClass 이다.

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.After
import org.junit.AfterClass
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

@RunWith((AndroidJUnit4::class))
class TestAnnotations {

    @Before
    fun setup() {
        println("Set Up function")
    }

    @Test
    fun test_a() {
        println("Test a")
    }

    @Test
    fun test_b() {
        println("Test b")
    }

    @After
    fun testAfter() {
        println("Test After Function")
    }

    companion object {
        @BeforeClass
        @JvmStatic
        fun setupClass() {
            println("set up Class")
        }

        @AfterClass
        @JvmStatic
        fun testAfterClass() {
            println("Test After Class")
        }
    }
}

 

실행 결과는 다음과 같다. @Before 은 각 테스트가 실행되기 전 실행되고 @After 은 각 테스트가 끝난 후 진행된다. @BeforeClass 는 테스트 클래스가 실행될 때 한 번 실행되고 @AfterClass 는 테스트가 모두 종료된 후 실행된다.

set up Class
started: test_a(com.example.wordsapp.TestAnnotations)
Set Up function
Test a
Test After Function
finished: test_a(com.example.wordsapp.TestAnnotations)
started: test_b(com.example.wordsapp.TestAnnotations)
Set Up function
Test b
Test After Function
finished: test_b(com.example.wordsapp.TestAnnotations)
Test After Class
728x90