涵盖单个应用的界面测试:这种类型的测试可验证目标应用在用户执行特定 *** 作或在其 Activity 中输入特定内容时的行为是否符合预期。
Espresso 会检测主线程何时处于空闲状态,以便可以在适当的时间运行测试命令,从而提高测试的可靠性
最近遇到这样的测试示例场景:
项目使用的是 Navigation (导航组件),点击 FragmentA 的按钮,导航到 FragmentB,FragmentB的 onViewCreated() 中有个方法,访问Api,如果Api有返回值,直接导航到 FragmentC,反之导航到 FragmentD。
很简单的逻辑:测试示例代码:
@LargeTest
@RunWith(AndroidJUnit4::class)
class SetupTests {
companion object {
const val SETUP_CONNECTING = "com.***.***.ui.setup.FragmentB"
}
@get:Rule
val activityScenarioRule = ActivityScenarioRule(Main::class.java)
@Test
fun testSetupConnectingWithIssue2() {
onView(ViewMatchers.withText("FragmentA's Text")).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
onView(ViewMatchers.withId(R.id.buttonAToB)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
.perform(ViewActions.click())
onView(ViewMatchers.withText("FragmentB's Text")).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
// 等待Api30秒的超时时间,该方法是自己封装的,下面有具体实现链接,感兴趣的可以去看下
IntegrationUtil.waitForElementToAppear(
onView(ViewMatchers.withText("FragmentC's Text")),
30
)
}
}
运行上面的test,发现存在问题:
⚠️ 如果网络状况一般,该测试可以运行通过;但是网络状况很好,可以知道的是 FragmentB,存在的时间极短,一闪而过。
onView(ViewMatchers.withText("FragmentB's Text")).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
分析:该验证 FragmentB的代码就会报错,提示找不到该元素,因为Espresso 会检测主线程何时处于空闲状态,等主线程有空闲的时候,才会执行该验证,此时的 FragmentB 已经跳转到FragmentC了。也就是说,这里如果继续使用Espresso来校验UI是不能成功的。
解决思路:测试 fragment 导航,类似如下代码:
@RunWith(AndroidJUnit4::class)
class TitleScreenTest {
@Test
fun testNavigationToInGameScreen() {
// Create a TestNavHostController
val navController = TestNavHostController(
ApplicationProvider.getApplicationContext())
// Create a graphical FragmentScenario for the TitleScreen
val titleScenario = launchFragmentInContainer()
titleScenario.onFragment { fragment ->
// Set the graph on the TestNavHostController
navController.setGraph(R.navigation.trivia)
// Make the NavController available via the findNavController() APIs
Navigation.setViewNavController(fragment.requireView(), navController)
}
// Verify that performing a click changes the NavController’s state
onView(ViewMatchers.withId(R.id.play_btn)).perform(ViewActions.click())
assertThat(navController.currentDestination?.id).isEqualTo(R.id.in_game)
}
}
使用 比较 currentDestination来验证FragmentB界面,但是我运行后,发现代码: navController.setGraph(R.navigation.trivia) 一直报错,而我也没能解决这个原因,就另辟蹊径;
如下代码:
@LargeTest
@RunWith(AndroidJUnit4::class)
class SetupTests {
companion object {
const val FRAGMENT_B = "com.***.***.ui.setup.FragmentB"
}
private var navController: NavController? = null
@get:Rule
val activityScenarioRule = ActivityScenarioRule(Main::class.java)
@Before
fun beforeTests() {
activityScenarioRule.scenario.onActivity {
navController = it.findNavController(R.id.nav_host)
}
}
@Test
fun testSetupConnectingWithIssue2() {
onView(ViewMatchers.withText("FragmentA's Text")).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
onView(ViewMatchers.withId(R.id.buttonAToB)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
.perform(ViewActions.click())
IntegrationUtil.waitForElementToAppear(
onView(ViewMatchers.withText("FragmentC's Text")),
30
)
val hasFragmentBDisplayed =
(navController?.previousBackStackEntry?.destination as? FragmentNavigator.Destination)?.className == FRAGMENT_B
Assert.assertTrue("verify ‘FragmentB’ screen has displayed", hasFragmentBDisplayed)
Thread.sleep(1000)
}
}
因为FragmentB显示时长的不确定性,不能直接去验证,包括使用currentDestination也是不行的,所以等FragmentC界面验证通过后,我们在验证 previousBackStackEntry 是不是 FragmentB,如果是,则经过FragmentB界面,反之则没有经过FragmentB。
补充:
1. Espresso具有同步功能:
每次测试调用 onView() 时,Espresso 都会等待执行相应的界面 *** 作或断言,直到满足以下同步条件:
- 消息队列为空。
- 没有当前正在执行任务的 AsyncTask 实例。
- 开发者定义的所有空闲资源都处于空闲状态。
通过执行这些检查,Espresso 大大提高了在任何给定时间只能发生一项界面 *** 作或断言的可能性。此功能可给您带来更可靠的测试结果。
2. waitForElementToAppear() 是自己封装的校验UI,
有兴趣的可以查看下一篇文章:Android Test - Espresso 延迟匹配的实现
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)