이 단계에서는 현재 탭의 레이블이 대문자로 표시되는지 확인한다.
가능한 해결책은 텍스트를 찾아(find) 대문자가 존재한다고 주장(assert)하는 것이다.
@Test
fun rallyTopAppBarTest_currentLabelExists() {
val allScreens = RallyScreen.values().toList()
composeTestRule.setContent {
RallyTopAppBar(
allScreens = allScreens,
onTabSelected = { },
currentScreen = RallyScreen.Accounts
)
}
composeTestRule
.onNodeWithText(RallyScreen.Accounts.name.uppercase(Locale.getDefault()))
.assertExists()
}
그러나 실행해보면 테스트가 실패하는 것을 확인할 수 있다.😱
이 단계에서는 semantics tree를 사용하여 디버깅하는 방법을 배운다.
Semantics tree
컴포즈 테스트는 semantics tree라는 구조를 사용하여 화면에서 요소를 찾고 해당 속성을 읽는다. 이는 TalkBack과 같은 서비스에서 읽을 수 있도록 접근성 서비스에서도 사용하는 구조다.
Warning: Semantics 속성에 대한 Layout Inspector 지원은 아직 사용할 수 없다.
노드에서 printToLog 함수를 사용하여 시맨틱 트리를 인쇄할 수 있다. 테스트에 새로운 라인을 추가하자.
fun rallyTopAppBarTest_currentLabelExists() {
val allScreens = RallyScreen.values().toList()
composeTestRule.setContent {
RallyTopAppBar(
allScreens = allScreens,
onTabSelected = { },
currentScreen = RallyScreen.Accounts
)
}
composeTestRule.onRoot().printToLog("currentLabelExists")
composeTestRule
.onNodeWithText(RallyScreen.Accounts.name.uppercase(Locale.getDefault()))
.assertExists() // 여전히 실패한다.
}
이제 테스트를 실행하고 Android Studio에서 Logcat을 확인하자.(currentLabelExists를 찾을 수 있다)
...com.example.compose.rally D/currentLabelExists: printToLog:
Printing with useUnmergedTree = 'false'
Node #1 at (l=0.0, t=63.0, r=1080.0, b=210.0)px
|-Node #2 at (l=0.0, t=63.0, r=1080.0, b=210.0)px
[SelectableGroup]
MergeDescendants = 'true'
|-Node #3 at (l=42.0, t=105.0, r=105.0, b=168.0)px
| Role = 'Tab'
| Selected = 'false'
| StateDescription = 'Not selected'
| ContentDescription = 'Overview'
| Actions = [OnClick]
| MergeDescendants = 'true'
| ClearAndSetSemantics = 'true'
|-Node #6 at (l=189.0, t=105.0, r=468.0, b=168.0)px
| Role = 'Tab'
| Selected = 'true'
| StateDescription = 'Selected'
| ContentDescription = 'Accounts'
| Actions = [OnClick]
| MergeDescendants = 'true'
| ClearAndSetSemantics = 'true'
|-Node #11 at (l=552.0, t=105.0, r=615.0, b=168.0)px
Role = 'Tab'
Selected = 'false'
StateDescription = 'Not selected'
ContentDescription = 'Bills'
Actions = [OnClick]
MergeDescendants = 'true'
ClearAndSetSemantics = 'true'
전체 앱의 semantics tree는 매우 길기 때문에, 이러한 접근방식으로 격리의 편리함을 누릴 수 있음을 이해하길 바란다.
Warning: 컴포저블에는 ID가 없고, 트리에 표시된 노드 번호를 사용하여 일치시킬 수 없다. semantics 속성과 노드를 일치시키는 것이 비실용적이거나 불가능한 경우, 마지막 수단으로 hasTestTag matcher와 함께 testTag Modifier를 사용할 수 있다.
Semantics 트리를 살펴보면, 상단 앱 바의 탭인 3개의 하위 요소가 있는 SelectableGroup이 있음을 알 수 있다. “ACCOUNTS” 값을 가진 텍스트 속성이 없다. 이것이 바로 테스트를 통과하지 못한 이유다. 그러나 각 탭에 대한 컨텐트 설명(content description)이 있다. TopAppBar.kt 내부의 RallyTab 컴포저블에서 이 속성이 어떻게 설정되어 있는지 확인할 수 있다.
private fun RallyTab(text: String...)
...
Modifier
.clearAndSetSemantics { contentDescription = text }
이 modifier는 하위 항목에서 속성을 지우고, 자체 content description을 설정하므로 “ACCOUNTS”가 아닌 “Accounts”가 표시된다.
finder인 onNodeWithText를 onNodeWithContentDescription으로 교체하고 테스트를 다시 실행해보자.
fun rallyTopAppBarTest_currentLabelExists() {
val allScreens = RallyScreen.values().toList()
composeTestRule.setContent {
RallyTopAppBar(
allScreens = allScreens,
onTabSelected = { },
currentScreen = RallyScreen.Accounts
)
}
composeTestRule
.onNodeWithContentDescription(RallyScreen.Accounts.name)
.assertExists()
}
축하합니다! 테스트를 수정하고 ComposeTestRule, 격리 테스트, finder, assertion 및 Semantics tree를 사용한 디버깅에 대해 배웠다.
하지만 나쁜 소식은 이 테스트가 그다지 유용하지 않다는 것이다! Semantics tree를 자세히 보면 탭이 선택되었는지 여부에 관계없이 세 개의 탭 모두에 대한 content description이 있다. 우리는 더 깊숙히 파고 들어가야 한다!
0개의 댓글