Testing your compose layout / testing cheat sheet
日付:2022/04/13
URL:https://developer.android.com/jetpack/compose/testing , https://developer.android.com/jetpack/compose/testing-cheatsheet
調査者:chigichan24
カテゴリ:Jetpack Compose Jetpack Compose Test
一言で表すと
cheat sheet を見よう!
https://developer.android.com/images/jetpack/compose/compose-testing-cheatsheet.pdf
概要
testの前にセマンティクスについて
semanticsは、アクセシビリティ対応の文脈で言われるようなやつ
UI階層における、「ひとまとまりの意味を持つもの」
https://scrapbox.io/files/6256c1648a89f1001de0f718.png
たとえば、このようなボタンがあるとする。
こういうセマンティクスのうち、 Text("LIKE") は自動的にLIKE という contetDescription が入るが、ボタン全体としては、特に contentDescriptionは設定されない。
設定するには、以下のように明示的に Modifier を指定する。
code:kotlin
Modifier.semantics { contentDescription = "Add to favorites" }
setup
androidx.compose以下にある ui-test-junit4 / ui-test-manifest を使う。
(junit5はどうなんでしょう...)
code:gradle
// Test rules and transitive dependencies:
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
// Needed for createComposeRule, but not createAndroidComposeRule:
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
こんな感じ
code:kotlin
class MyComposeTest {
@get:Rule
val composeTestRule = createComposeRule()
// use createAndroidComposeRule<YourActivity>() if you need access to
// an activity
@Test
fun MyTest() {
// Start the app
composeTestRule.setContent {
MyAppTheme {
MainScreen(uiState = fakeUiState, /*...*/)
}
}
composeTestRule.onNodeWithText("Continue").performClick()
composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()
}
}
TestAPI
Test APIは主に4つの属性がある。
Finders
1 つまたは複数の要素(セマンティクス ツリー内のノード)を選択して、アサーションを行ったり、アクションを実行したりできる。
onNodeやonAllNodes、onNodeWithContentDescription、onNodeWithTextなど。
Go.iconテストを書くためにcontentDescriptionを入れるように心がけねば
code example
code:kotlin
composeTestRule
.onNode(hasText("Button")) // Equivalent to onNodeWithText("Button")
ちなみに、onNode系のメソッドには、useUnmergedTreeというパラメータが生えている。
これは、デフォルトは false
true にすることで、merge されていた semantics を、最小単位で走査できるっぽい
In case of false
code:kotlin
MyButton {
Text("Hello")
Text("World")
}
composeTestRule.onRoot().printToLog("TAG")
/**
* Node #1 at (...)px
* |-Node #2 at (...)px
* Role = 'Button'
* Text = 'Hello, World'
* Actions = OnClick, GetTextLayoutResult
* MergeDescendants = 'true'
**/
In case of true
code:text
Node #1 at (...)px
|-Node #2 at (...)px
OnClick = '...'
MergeDescendants = 'true'
|-Node #3 at (...)px
| Text = 'Hello'
|-Node #5 at (83.0, 86.0, 191.0, 135.0)px
Text = 'World'
Assertions
要素が存在すること、または特定の属性を持つことの確認に使用する。
assert、assertIsDisplayedなど
code example
code:kotlin
// Single matcher:
composeTestRule
.onNode(matcher)
.assert(hasText("Button")) // hasText is a SemanticsMatcher
// Multiple matchers can use and / or
composeTestRule
.onNode(matcher).assert(hasText("Button") or hasText("Button2"))
// Check number of matched nodes
composeTestRule
.onAllNodesWithContentDescription("Beatle").assertCountEquals(4)
// At least one matches
composeTestRule
.onAllNodesWithContentDescription("Beatle").assertAny(hasTestTag("Drummer"))
// All of them match
composeTestRule
.onAllNodesWithContentDescription("Beatle").assertAll(hasClickAction())
Actions
要素に対するクリックやジェスチャーなどのユーザー イベントをシミュレートするためのもの
performScrollTo()、performClick() とか。
Note: You cannot chain actions inside a perform function. Instead, make multiple perform() calls.
performXXは、chainできないらしいので、代わりに performを複数回呼び出してとのこと
地味に厳しい :(
Matchers
セマンティクス ツリーを上下に移動していい感じに node を探す Hierarchical matchers (階層的マッチャー)
filter などのセレクタもある
同期について
ComposeTestでは仮想クロックを使って時間を制御している
そしてcomposetTestの仕様有無がrecomposionを制御するようになっているため、このことを知らないと微妙にバグることがある
code:kotlin
@Test
fun counterTest() {
val myCounter = mutableStateOf(0) // State that can cause recompositions
var lastSeenValue = 0 // Used to track recompositions
composeTestRule.setContent {
Text(myCounter.value.toString())
lastSeenValue = myCounter.value
}
myCounter.value = 1 // The state changes, but there is no recomposition
// Fails because nothing triggered a recomposition
assertTrue(lastSeenValue == 1)
// Passes because the assertion triggers recomposition
composeTestRule.onNodeWithText("1").assertExists()
}
特殊ケースをテストしたいときは自動同期を off にしよう!
code:kotlin
composeTestRule.mainClock.autoAdvance = false
このときは自分で時間を進める必要があるので、以下を使ってください (coroutines testみたいなのりっぽい)
code:kotlin
composeTestRule.mainClock.advanceTimeByFrame()
composeTestRule.mainClock.advanceTimeBy(milliseconds)
Espressoとの併用
特に特別な設定なく併用できるらしい
code:kotlin
@Test
fun androidViewInteropTest() {
// Check the initial state of a TextView that depends on a Compose state:
Espresso.onView(withText("Hello Views")).check(matches(isDisplayed()))
// Click on the Compose button that changes the state
composeTestRule.onNodeWithText("Click here").performClick()
// Check the new value
Espresso.onView(withText("Hello Compose")).check(matches(isDisplayed()))
}
気になるポイント
メモ
コメント
mkeeda.icon Composeはsemanticsでテストを書くので、テスト書いてたら自然とアクセシビリティを意識するようになっていいっすね