onNode, onNodeWithTag, onNodeWithText, onNodeWithContentDescription을 통해 UI 노드를 찾을 때의 한계
onNode- 구문을 사용해 UI 노드를 찾으면 한 번에 한 개의 노드 밖에 찾기 못한다. 예를 들어 다음과 컴포저블에 대해 Smile이라는 텍스트를 가진 모든 UI 노드를 찾아 클릭하는 테스트를 해야 한다고 해보자.
class OnAllNodesTest {
@get:Rule
val composeRule = createComposeRule()
@Test
fun onNodeAllNodes() {
// Given
val isClicked = Array(4) { false }
composeRule.setContent {
Column() {
EmojiText(
modifier = Modifier.clickable {
isClicked[0] = true
},
emoji = "😁",
content = "Smile"
)
EmojiText(
modifier = Modifier.clickable {
isClicked[1] = true
},
emoji = "😆",
content = "Smile"
)
EmojiText(
modifier = Modifier.clickable {
isClicked[2] = true
},
emoji = "😎",
content = "Smile"
)
EmojiText(
modifier = Modifier.clickable {
isClicked[3] = true
},
emoji = "😃",
content = "Smile"
)
}
}
// When
// Then
}
}
이 컴포저블로 만들어진 UI의 모양은 다음과 같다.
만약 onNode- 구문을 사용해 찾아야 한다면, 다음과 같은 방식으로 각 노드를 찾아 클릭을 실행해야 한다.
// When
composeRule.onNodeWithText("😁").performClick()
composeRule.onNodeWithText("😆").performClick()
composeRule.onNodeWithText("😎").performClick()
composeRule.onNodeWithText("😃").performClick()
하지만, 이 방식은 다음과 같은 문제점이 있다.
- Smile이라는 텍스트를 가진 모든 노드를 찾아 클릭을 실행해야 하는데, 그와 관련 없는 이모지를 구분자로 사용해 UI 노드를 찾고 클릭 이벤트를 발생시켰다.
- 같은 코드가 여러 번 반복된다.
특히 1번의 경우 코드의 가독성 측면과 유지 보수성 측면에서 치명적이다. 다음 개발자가 왔을 때 이 테스트가 "Smile"을 가진 모든 Composable을 찾아 클릭하는 테스트라는 것을 코드만 보고 알기 어려울 것이다.
onAllNodes- 구문 사용해 특정 조건을 만족하는 모든 UI 노드 찾기
이렇게 특정 조건을 만족하는 모든 UI 노드를 찾기 위한 해답은 바로 onAllNodes- 구문을 사용하는 것이다. 하나의 노드를 찾
기 위해 onNode, onNodeWithTag, onNodeWithText, onNodeWithContentDescription 가 있는 것처럼, onAllNodes- 구문도 onAllNodes, onAllNodesWithTag, onAllNodesWithText, onAllNodesWithText 가 있으며, 사용 방법은 onNode- 구문과 같다. 다른 점은 onAllNodes- 구문은 복수의 UI 노드를 찾는다는 점이다.
그러면 직전에 다룬 테스트의 요구 사항인 "Smile"이라는 텍스트를 가진 모든 노드를 찾아 클릭하는 UI 테스트를 onAllNodes- 구문을 사용해 작성해 보자. 특정 문자열을 가진 UI 노드를 찾아야 하므로 onAllNodesWithText를 사용한다. 그러면 테스트는 다음과 같아진다.
@Test
fun onNodeAllNodesClicked() {
// Given
...
// When
val nodes = composeRule.onAllNodesWithText("Smile")
for (index in 0 until nodes.fetchSemanticsNodes().size) {
nodes[index].performClick()
}
// Then
assertTrue(isClicked.all { clicked -> clicked })
}
* 위 코드에서 forEach를 사용하지 않은 이유는, onAllNodesWith- 구문은 SemanticsNodeInteractionCollection 타입의 객체를 반환하는데, 이 객체에 대한 forEach 확장함수가 정의되어 있지 않기 때문에 위와 같이 for문을 통해 각 노드를 순환하는 방법을 채택했다.
이 테스트에서는 onAllNodesWithText("Smile")을 사용해 Smile이라는 텍스트를 가진 모든 UI 노드를 찾고, 해당 노드를 순환하며, 클릭을 발생시킨다.
이제 테스트를 실행시켜보면, 다음과 같은 결과가 나오는 것을 볼 수 있다.
모든 노드를 순환해 클릭 이벤트가 발생해 테스트가 통과된다.