이전 시간에 배운 내용
이전 시간에 우리는 JUnit에서 지원하는 @get:Rule 어노테이션과 함께 createComposeRule을 사용해 ComposeContentTestRule 객체를 만듦으로써 Composable 테스트 환경을 구축하는 방법에 대해 알아보았다.
이후에는 ComposeContentTestRule 객체를 사용해 onNodeWithText를 통해 UI 컴포넌트를 찾는 방법에 대해 다루었다. 이번 시간에는 onNodeWith- 구문을 사용해 더욱 다양한 방법으로 UI 컴포넌트를 찾는 방법에 대해 알아본다.
onNodeWithText의 한계
onNodeWithText는 매우 편리한 기능이지만, 텍스트가 있는 컴포넌트를 찾을 때만 쓸 수 있다. 모든 UI 컴포넌트가 텍스트를 가지는 것은 아니기 때문에, 이 방법은 한계가 있다. 예를 들어 다음과 같은 CirclePlayButton Composable이 있다고 해보자. 이 Composable에는 텍스트가 없기 때문에 onNodeWithText로 Composable을 찾기 어렵다.
@Composable
fun CirclePlayButton(
modifier: Modifier = Modifier,
boxSize: Dp,
iconSize: Dp = boxSize,
boxColor: Color = MaterialTheme.colorScheme.secondary,
iconColor: Color = MaterialTheme.colorScheme.onSecondary,
onClick: () -> Unit
) {
Box(
modifier = modifier
.size(size = boxSize)
.clip(CircleShape)
.background(boxColor)
.clickable {
onClick.invoke()
}
) {
Icon(
modifier = Modifier
.size(iconSize)
.align(Alignment.Center),
painter = painterResource(id = R.drawable.baseline_play_arrow_24),
tint = iconColor,
contentDescription = null
)
}
}
이 버튼이 제대로 클릭되는지 알기 위해서는 화면에서 이 컴포저블을 찾아 버튼을 클릭하고, 이벤트가 제대로 반환되는지 알 수 있어야 하는데 onNodeWithText로는 이것을 알 수 없다. 이를 해결하기 위해 onNodeWithText 말고도 UI 노드를 찾는 여러 방법이 제공된다. 지금부터 그 방법들을 알아보자.
UI 노드에 Content Description 설정하기
이렇게 텍스트가 없는 UI 노드를 찾는 첫 방법은 onNodeWithContentDescription을 사용하는 것이다. 안드로이드에서 Content Description은 다양한 곳에 사용되는 문자열이다. 대표적으로 Talk Back 기능을 통해 시각 장애인 분들이 안드로이드 화면을 클릭했을 때 어떤 컴포넌트를 클릭했는지 듣는데도 사용되며, 일부 기기에서는 툴팁을 보여주는 데 사용되기도 했었다.
*Android8.0부터는 tooltip 속성을 통해 툴팁을 보여준다. 참고
UI 테스트를 위해서는 어떤 컴포넌트를 직접 보지 않고 찾는 것이 필요하므로, 이 Content Description을 사용하는 것은 매우 유용하다. 먼저 Composable에 Content Description을 사용하는 방법부터 시작하자. Composable에서 Content Description을 설정하기 위해서는 Content Description 설정을 원하는 UI 노드의 Modifier에 semantic을 통해 contentDescription 프로퍼티를 설정해줘야 한다.
modifier = Modifier
.semantics {
this.contentDescription = "CirclePlayButton"
}
이를 앞서 살펴본 CirclePlayButton Composable에 content description을 Circle Play Button으로 설정하면 다음과 같아진다.
@Composable
fun CirclePlayButton(
modifier: Modifier = Modifier,
boxSize: Dp,
iconSize: Dp = boxSize,
boxColor: Color = MaterialTheme.colorScheme.secondary,
iconColor: Color = MaterialTheme.colorScheme.onSecondary,
onClick: () -> Unit
) {
Box(
modifier = modifier
.size(size = boxSize)
.clip(CircleShape)
.background(boxColor)
.semantics {
this.contentDescription = "Circle Play Button"
}
.clickable {
onClick.invoke()
}
) {
Icon(
modifier = Modifier
.size(iconSize)
.align(Alignment.Center),
painter = painterResource(id = R.drawable.baseline_play_arrow_24),
tint = iconColor,
contentDescription = null
)
}
}
자 이제 UI 테스트에서 이 노드에 대해 Content Description을 사용해 접근이 가능해진다. 본격적으로 테스트를 작성해보자.
onNodeWithContentDescription을 사용해 UI 노드 찾기
Content Description을 사용해 UI 노드를 찾으려면 onNodeWithContentDescription 함수를 사용해야 한다. 따라서 앞서 작성한 Circle Play Button은 다음 구문을 통해 찾을 수 있다.
composeRule.onNodeWithContentDescription("Circle Play Button")
자 이제 본격적으로 테스트를 작성해보자. CirclePlayButton Composable이 제대로 동작하는지 확인하기 위해서는 CirclePlayButton을 클릭하고 클릭 이벤트가 제대로 동작하는지 확인해야 한다. testCircleButtonClick를 다음과 같이 작성해보자.
@Test
fun testCircleButtonClick() {
// Given
var isClicked = false
composeRule.setContent {
CirclePlayButton(
boxSize = 48.dp,
iconSize = 36.dp
) {
isClicked = true
}
}
// When
composeRule.onNodeWithContentDescription("Circle Play Button").performClick()
// Then
assertTrue(isClicked)
}
이 테스트에서는 CirclePlayButton을 만든 다음, 화면에서 해당 CirclePlayButton Composable의 Content Description인 Circle Play Button을 사용해 UI Node를 찾아 performClick을 통해 클릭을 실행하고 있다. 그러면 이제 테스트가 실행되고 통과되는 것을 볼 수 있다.