이 단계에서는 액션(Action)을 사용하여 RallyTopAppBar의 다른 탭을 클릭하면 선택 항목이 변경되는지 확인한다. Action에 대한 부분은 Testing Cheat Sheet를 참조하자.

힌트:

  • 테스트 범위에는 RallyApp이 소유한 상태(State)가 포함되어야 한다.
  • 행동(behavior)이 아니라 상태(state)를 확인(verify)하자. 호출된 객체와 방법에 의존하는 대신 UI 상태에 대한 주장(assertion)을 사용한다.

이 연습문제에는 제공된 해답이 없다.


정답이 없지만 열심히 연습문제를 풀어보겠습니다. 태클 환영합니다.

주어진 힌트가 사실은 제약사항인 것 같다.

RallyApp이 가지고 있는 상태를 반드시 포함해서 테스트 범위를 잡아야 하고, 상단 바의 탭을 클릭해서 해당 상태가 변경되었는지도 확인해야 하니 우선 RallyApp코드를 살펴본다.

@Composable
fun RallyApp() {
    RallyTheme {
        val allScreens = RallyScreen.values().toList()
        var currentScreen by rememberSaveable { mutableStateOf(RallyScreen.Overview) }
        Scaffold(
            topBar = {
                RallyTopAppBar(
                    allScreens = allScreens,
                    onTabSelected = { screen -> currentScreen = screen },
                    currentScreen = currentScreen
                )
            }
        ) { innerPadding ->
            Box(Modifier.padding(innerPadding)) {
                currentScreen.content(onScreenChange = { screen -> currentScreen = screen })
            }
        }
    }
}

코드를 살펴보면 첫번째 힌트에 나온 상태가 currentScreen임을 알 수 있다. currentScreen에 따라 RallyTopAppBar가 영향을 받고 있고, 또 RallyTopAppBar의 탭을 클릭함에 따라 상태가 변경되는 것을 알 수 있다.

RallyApp은 상태를 갖고 있기 때문에 stateful하다. stateful한 컴포저블 함수는 테스트 하기 어렵다. 테스트에서 해당 상태에 액세스 하기 어렵기 때문이다. 상태를 끌어올려(state hoisting) 테스트 가능하도록 변경해보자.

@Composable
fun RallyApp(currentScreen:RallyScreen, onTabSelected: (RallyScreen) -> Unit) {
    RallyTheme {
        val allScreens = RallyScreen.values().toList()

        Scaffold(
            topBar = {
                RallyTopAppBar(
                    allScreens = allScreens,
                    onTabSelected = onTabSelected,
                    currentScreen = currentScreen
                )
            }
        ) { innerPadding ->
            Box(Modifier.padding(innerPadding)) {
                currentScreen.content(onScreenChange = onTabSelected)
            }
        }
    }
}

Note: state hoisting은 상태를 끌어올리기 위한 일반적 패턴은 컴포저블 함수 내에 선언된 상태 변수를 해당 컴포저블 함수의 두 개의 매개변수로 바꾸는 것이다.

1. value: T : 표시할 현재 값. 위의 코드에서 currentScreen에 해당
2. onValuChange: (T) -> Unit : T가 제안된 새값인 경우 값을 변경하도록 요청하는 이벤트. 위의 코드에서 onTabSelected에 해당

RallyApp을 변경하였으므로 RallyActivity의 코드도 다음과 같이 변경하자.

class RallyActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            var currentScreen:RallyScreen by rememberSaveable { mutableStateOf(RallyScreen.Overview) }
            RallyApp(currentScreen){
                screen-> currentScreen = screen
            }
        }
    }
}

이제 테스트 코드를 작성하자.

...
@get:Rule
val composeTestRule = createComposeRule()

@Test
fun rallyTopAppBarTest_clickTabs(){
    var currentScreen:RallyScreen = RallyScreen.Overview // 현재 상태
    composeTestRule.setContent { 컴포즈 테스트 룰에 RallyApp 설정하기
        RallyApp(currentScreen){ screen-> currentScreen = screen }
    }

    // 모든 탭을 순회하면서 클릭 하고 현재 상태를 확인한다.
    RallyScreen.values().forEach { screen->
        composeTestRule
            .onNodeWithContentDescription(screen.name)
            .performClick()
        assert(currentScreen == screen)
    }
}

onNoWithContentDescription 파인더(finder)를 통해 특정 탭을 찾고, performClick()을 호출하여 해당 탭을 클릭한다. 클릭한 후 currentScreen이 올바르게 변경되었는지 확인한다.

카테고리: Compose

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.