일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- OZViewer
- socket-client
- 오즈뷰어
- Kotlin
- NoSuchMethodError
- TIZEN
- 워치
- mqtt
- Firebase
- socket-server
- hung-up
- AWS
- Dva
- Galaxy Watch
- Android
- BottomSheetDialog
- Flavors
- cloud-firestore
- ozd
- google-login
- Java8
- JNI
- ActivityResult-API
- git
- firebase-database
- gradle
- firebase-storage
- socket.io
- mosquitto
- git-push
- Today
- Total
Hyeyeon blog
[Android] Compose UI Test 본문
1. Compose의 Semantics 이란,
Jetpack Compose의 UI 요소는 계층적인 시맨틱 트리(Semantics Tree)로 구성되며, 각 요소는 SemanticsNode로 표현됩니다. UI 테스트에서는 이 시맨틱 트리를 탐색하여 특정 요소(시맨틱 노드)를 찾아 테스트를 수행합니다.
아래와 같이, Column, Text, Button, Row 같은 시맨틱 요소들이 등록되어 트리를 구성하며, UI 테스트 시 이를 탐색합니다.
Root
├── Column
│ ├── Text("Hello")
│ ├── Button("Click!")
│ └── Row
│ ├── Text("Item 1")
│ └── Text("Item 2")
🔎 시맨틱 트리 시각화
시맨틱 트리를 시각화하여 Logcat으로 확인할 수 있습니다.
1) dependency 추가
debugImplementation("androidx.compose.ui:ui-tooling:1.7.8")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.7.8")
debugImplementation("androidx.compose.ui:ui-test-manifest:1.7.8")
2) Compose 테스트 코드 작성
class ComposeTest {
@get:Rule
val composeTestRule = createComposeRule()
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
@Test
fun sementicTree() {
composeTestRule.setContent {
Theme {
Column {
Text(text = "Hello")
Button(
onClick = {
Toast.makeText(appContext, "Show Toast", Toast.LENGTH_SHORT).show()
},
) {
Text("Click!")
}
Row {
Text(text = "Item1")
Text(text = "Item2")
}
}
}
}
}
}
3) Logcat에서 시맨틱 트리 출력
2. Compose 테스트 구성 요소
1) Finder: UI 컴포넌트를 찾음
- 예시
composeTestRule.onNodeWithText("Hello")
- 주요 함수
- onNodeWithText("텍스트")
- onNodeWithTag("태그")
- onNodeWithContentDescription("설명")
- onAllNodesWithText("텍스트")
※ 해당 텍스트를 가진 컴포넌트의 바로 윗 상위 노드 반환
※ useUnmergedTree = true 시, 해당 컴포넌트 노드만 반환
2) Assertion: 찾은 UI 컴포넌트가 기대하는 상태인지 확인
- 예시
composeTestRule
.onNodeWithText("Hello")
.assertIsDisplayed()
- 주요 함수
- assertIsDisplayed() – 화면에 보이는지 확인
- assertTextEquals("텍스트") – 텍스트가 정확히 일치하는지
- assertHasClickAction() – 클릭이 가능한지 (Modifier.clickable 설정 여부)
- assertIsEnabled() / assertIsNotEnabled() – 활성화 여부
3) Action: 찾은 UI 컴포넌트를 대상으로 동작을 수행
- 예시
composeTestRule
.onNodeWithText("Click!")
.performClick()
- 주요 함수
- performClick() – 클릭
- performTextInput("입력값") – 텍스트 입력
- performScrollTo() – 스크롤
- performTextClearance() – 입력된 텍스트 지우기
3. Jetpack Compose UI 테스트 환경 설정
build.gradle에 ui-test-junit4, ui-test-manifest dependency를 추가합니다.
dependencies {
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.0.5"
debugImplementation "androidx.compose.ui:ui-test-manifest:1.0.5"
}
ComposeTestRule를 설정합니다.
이는 JUnit 테스트에서 Compose 환경을 구성하고 테스트 함수에서 UI 조작과 검증을 수행합니다.
class ExampleInstrumentedTest {
@get:Rule
val composeTestRule = createComposeRule()
...
}
4. Jetpack Compose UI 테스트 예제
로그인 화면에서 이메일, 비밀번호를 모두 입력하면 로그인 버튼이 활성화되는 폼 검증 시나리오입니다.
1) 컴포즈 코드
이메일, 비밀번호 필드의 입력 값을 추적하기 위한 상태변수를 선언합니다.
이어, 버튼 활성화 여부에 사용될 이메일과 비밀번호가 모두 입력되었는지 확인하는 변수를 선언합니다.
@Composable
fun LoginScreen() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
// 이메일과 비밀번호가 모두 입력되었는지 확인하는 변수
val isFormValid = email.isNotEmpty() && password.isNotEmpty()
Column(modifier=Modifier.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally) {
// 이메일 입력 필드
TextField(
value = email,
onValueChange = { email = it },
modifier=Modifier.border(1.dp, Color.Black)
.testTag("Email")
)
Spacer(modifier = Modifier.height(8.dp))
// 비밀번호 입력 필드
TextField(
value = password,
onValueChange = { password = it },
modifier=Modifier.border(1.dp, Color.Black)
.testTag("Password")
)
Spacer(modifier = Modifier.height(8.dp))
// 로그인 버튼
Button(
onClick = { /* 로그인 처리 로직 */ },
enabled = isFormValid,
modifier=Modifier.testTag("LoginButton")
) {
Text(text = "Login")
}
}
}
2) 검증 코드
동작/상태 검증을 위한 테스트 코드를 작성합니다.
@Test
fun composeUiTest() {
composeTestRule.setContent {
LoginScreen()
}
// 로그인 버튼 상태 확인 - 결과: 비활성화 (초기상태)
composeTestRule.onNodeWithTag("LoginButton").assertIsNotEnabled()
// 이메일 필드에 입력
composeTestRule.onNodeWithTag("Email").performTextInput("test@gmail.com")
// 로그인 버튼 상태 확인 - 결과: 비활성화 (이메일만 입력)
composeTestRule.onNodeWithTag("LoginButton").assertIsNotEnabled()
// 비밀번호 필드에 입력
composeTestRule.onNodeWithTag("Password").performTextInput("12341234")
// 로그인 버튼 상태 확인 - 결과: 활성화 (이메일, 비밀번호 입력)
composeTestRule.onNodeWithTag("LoginButton").assertIsEnabled()
}
3) 결과
위와 같이 예상한 시나리오대로 동작하면 아래와 같이 passed 상태를 확인할 수 있습니다.
만일 예상한 시나리오와 다르게 동작할 경우, 아래와 같이 failed 상태와 실패 로그가 발생합니다.
'개발 > Android' 카테고리의 다른 글
MVI Architecture Pattern in Android (0) | 2025.03.01 |
---|---|
이펙티브 코틀린 - 5장 객체생성, 6장 클래스 설계 (0) | 2024.04.09 |
'compileDebugJavaWithJavac' task (current target is 1.8) and 'kaptGenerateStubsDebugKotlin' task (current target is 17) jvm target compatibility should be set to the same Java version. (0) | 2024.04.08 |
이펙티브 코틀린 - 3장 재사용성, 4장 추상화 설계 (0) | 2024.04.08 |
이펙티브 코틀린 - 2장 가독성 (0) | 2024.03.10 |