如何实时运行Jetpack Compose测试



在Android上使用Jetpack Compose。

我有一个测试,它模拟了一个列中可组合的几个文本的选择。

选择从长按第一个项目开始,然后向下移动到更多可组合文本上,并在列内停止。

通常测试应该在无人值守的情况下快速运行。但我希望能够实时显示选择过程(出于演示目的,也为了看看它是否像设计的那样工作,例如,一开始我忘记了在down((之后必须等待一段时间(。

列中可组合的第一个Text也用于查找元素(->锚点(,父级是用于执行移动的column。

这是执行选择的功能:

val duration = 3000L
val durationLongPress = 1000L
fun selectVertical(anchor: SemanticsNodeInteraction, parent: SemanticsNodeInteraction) {
anchor.performTouchInput {
down(center)
}
clock.advanceTimeBy(durationLongPress)
// the time jumps here, but the selection of the first word is visible
val nSteps = 100
val timeStep = (duration-durationLongPress)/nSteps
parent.performTouchInput {
moveTo(topCenter)
val step = (bottomCenter-topCenter)*0.8f/ nSteps.toFloat()
repeat(nSteps) {
moveBy(step, timeStep)
}
up()
}
}

这是可堆肥的:

@Composable
fun SelectableText() {
val text = """
|Line
|Line start selecting here and swipe over the empty lines
|Line or select a word and extend it over the empty lines
|Line
|
|
|
|Line
|Line
|Line
|Line
""".trimMargin()
Column {
SelectionContainer {
Column {
Text("simple")
Text(text = text)   // works
}
}
SelectionContainer {
Column {
Text("crash")
text.lines().forEach {
Text(text = it)
}
}
}
SelectionContainer {
Column {
Text("space")
text.lines().forEach {
// empty lines replaced by a space works
Text(text = if (it == "") " " else it)
}
}
}
}
}

一个测试是这样的:

@Test
fun works_simple() {
val anchor = test.onNodeWithText("simple")
val textNode = anchor.onParent()
textNode.printToLog("simple")
controlTime(duration) {
selectVertical(anchor, textNode)
}
}

controlTime是不起作用的部分。我在这里添加它的代码并不是为了保持解决方案的开放性。

我试图在虚拟测试时钟上禁用autoAdvance,并在协程中循环计时。

当我以1ms的步长计算时间并添加每个延迟(1(时,等待是正确的,但我没有看到选择范围扩大(我至少想看到步长(。相反,我看到的是第一个单词的选择,然后什么都没有,直到移动结束,然后是最终结果。

我还将移动划分为较小的步骤,例如10或100,但仍然没有显示结果。

好吧,我自己在睡觉时找到了解决方案。。。";睡眠;大脑显然在处理未解决的问题(好吧,我已经知道了(。

关键是,做每一个动作都应该在自己的表演中可见XXX。我认为,只有当代码块完成时,结果才会传播到UI,这对测试来说是有意义的。

parent.performTouchInput {
inRealTime("moveBy($step, $timeStep)", timeStep) {
moveBy(step)
}
}

我找不到一种方法来确定所谓的";帧";,所以我提前虚拟时钟或实际时钟,这取决于哪一个滞后,直到两者都达到目标时间。这可能可以优化为在一步中跳过两个时钟。我稍后再调查。

有趣的是,即使100步也不能显示出平滑的选择动作。相反,即使步骤时间增加,仍然只有几个步骤。

Btw。这段代码的目的是在SelectionContainer中显示一个崩溃,当它遇到一个空的Text("")可用于我创建的错误报告时。我会在问题跟踪器上提供它,但我也想在我们的应用程序开发中进行测试,看看它何时得到解决,并避免出现不起作用的库。有时我们会在库中遇到倒退,例如,如果修复程序有错误。

这是完整的测试代码:

package com.example.myapplication
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Before
import org.junit.Rule
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class CrashTest {

val duration = 3000L
val durationLongPress = 1000L
@Composable
fun SelectableText() {
val text = """
|Line
|Line start selecting here and swipe over the empty lines
|Line or select a word and extend it over the empty lines
|Line
|
|
|
|Line
|Line
|Line
|Line
""".trimMargin()
Column {
SelectionContainer {
Column {
Text("simple")
Text(text = text)   // works
}
}
SelectionContainer {
Column {
Text("crash")
text.lines().forEach {
Text(text = it)
}
}
}
SelectionContainer {
Column {
Text("space")
text.lines().forEach {
// empty lines replaced by a space works
Text(text = if (it == "") " " else it)
}
}
}
}
}
@Rule
@JvmField
var test: ComposeContentTestRule = createComposeRule()
@Before
fun setUp() {
test.setContent { SelectableText() }
test.onRoot().printToLog("root")
}
val clock get() = test.mainClock
fun inRealTime(what: String? = null, duration: Long = 0, todo: () -> Unit) {
clock.autoAdvance = false
what?.let { Log.d("%%%%%%%%%%", it) }
val startVirt = clock.currentTime
val startReal = System.currentTimeMillis()
todo()
while (true) {
val virt = clock.currentTime - startVirt
val real = System.currentTimeMillis() - startReal
Log.d("..........", "virt: $virt real: $real")
if (virt > real)
Thread.sleep(1)
else
clock.advanceTimeByFrame()
if ((virt > duration) and (real > duration))
break
}
clock.autoAdvance = true
}
fun selectVertical(anchor: SemanticsNodeInteraction, parent: SemanticsNodeInteraction) {
inRealTime("down(center)", durationLongPress) {
anchor.performTouchInput {
down(center)
}
}
val nSteps = 100
val timeStep = (duration-durationLongPress)/nSteps
Log.d("----------", "timeStep = $timeStep")
var step = Offset(1f,1f)
parent.performTouchInput {
step = (bottomCenter-topCenter)*0.8f/ nSteps.toFloat()
}
repeat(nSteps) {
parent.performTouchInput {
inRealTime("moveBy($step, $timeStep)", timeStep) {
moveBy(step)
}
}
}
parent.performTouchInput {
inRealTime("up()") {
up()
}
}
}
@Test
fun works_simple() {
val anchor = test.onNodeWithText("simple")
val textNode = anchor.onParent()
textNode.printToLog("simple")
selectVertical(anchor, textNode)
}
@Test
fun crash() {
val anchor = test.onNodeWithText("crash")
val textNode = anchor.onParent()
textNode.printToLog("crash")
selectVertical(anchor, textNode)
}
@Test
fun works_space() {
val anchor = test.onNodeWithText("space")
val textNode = anchor.onParent()
textNode.printToLog("space")
selectVertical(anchor, textNode)
}
}

最新更新