杰克沃顿发表了一个引人入胜的演讲,他提出了一些聪明的方法来改进我们的UI测试,抽象出我们如何从测试中执行UI的细节:https://news.realm.io/news/kau-jake-wharton-testing-robots/
他给出的一个例子是一个看起来如下的测试,其中PaymentRobot对象包含付款金额和&的详细信息.收件人进入用户界面.把它放在一个位置很有意义所以当UI不可避免地发生变化时(例如重命名字段ID,或从TextEdit切换到TextInputLayout),它只需要在一个地方更新而不是一系列测试.它还使测试更简洁,更易读.他建议使用Kotlin使它们更加平稳.我不使用Kotlin,但仍希望从这种方法中受益.
@Test public void singleFundingSourceSuccess {
PaymentRobot payment = new PaymentRobot();
ResultRobot result = payment
.amount(42_00)
.recipient("foo@bar.com")
.send();
result.isSuccess();
}
他提供了如何构造Robot类的概述,使用显式的isSuccess()响应,返回另一个Robot,它是下一个屏幕,或者当前状态的状态:
class PaymentRobot {
PaymentRobot amount(long amount) { ... }
PaymentRobot recipient(String recipient) { .. }
ResultRobot send() { ... }
}
class ResultRobot {
ResultRobot isSuccess() { ... }
}
我的问题是:
> Robot如何与Activity / Fragment接口,具体在哪里实例化?我希望这会在跑步者的测试中发生,但他的例子似乎暗示了其他方面.这种方法看起来非常有用,但我不知道如何在实践中实现它,无论是对于单个Activity / Fragment还是对于它们的序列.
>如何扩展此方法,以便isSuccess()方法可以处理各种方案.例如如果我们正在测试登录屏幕,那么isSuccess()如何处理各种预期结果,例如:身份验证成功,API网络故障和身份验证失败(例如403服务器响应)?理想情况下,API将在Retrofit后面进行模拟,并且每个结果都使用端到端UI测试进行测试.
除了Jake的概述之外,我还没有找到任何实现的例子.
解决方法:
我完全误解了Espresso的工作原理,这让我更加困惑于如何将它应用到页面对象模式中.我现在看到Espresso不需要对测试中的Activity进行任何类型的引用,只是在runner规则的上下文中运行.对于其他任何苦苦挣扎的人来说,这里有一个充实的例子,将机器人/页面对象模式应用于登录屏幕上的验证测试,其中包含用户名和密码字段,我们正在测试当任一字段为何时显示错误消息空:
public class LoginRobot {
public LoginRobot() {
onView(withId(R.id.username)).check(matches(isdisplayed()));
}
public void enterUsername(String username) {
onView(withId(R.id.username)).perform(replaceText(username));
}
public void enterPassword(String password) {
onView(withId(R.id.password)).perform(replaceText(password));
}
public void clickLogin() {
onView(withId(R.id.login_button)).perform(click());
}
}
(注意构造函数正在测试以确保当前屏幕是我们期望的屏幕)
LoginValidationTests.java:
@LargeTest
@RunWith(AndroidJUnit4.class)
public class LoginValidationTests {
@Rule
public ActivityTestRule<LoginActivity> mActivityTestRule = new ActivityTestRule<>(LoginActivity.class);
@Test
public void loginPasswordValidationtest() {
LoginRobot loginPage = new LoginRobot();
loginPage.enterPassword("");
loginPage.enterUsername("123");
loginPage.clickLogin();
onView(withText(R.string.login_bad_password))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
}
@Test
public void loginUsernameValidationtest() {
LoginRobot loginPage = new LoginRobot();
loginPage.enterUsername("");
loginPage.enterPassword("123");
loginPage.clickLogin();
onView(withText(R.string.login_bad_username)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
}
}
抽象出像这样自动化UI的机制可以避免跨测试的大量重复,也意味着更改不太可能需要反映在许多测试中.例如如果布局ID发生更改,则只需要更新机器人类,而不是每个引用该字段的测试.测试也明显更短,更易读.
机器人方法,例如,登录按钮方法,可以返回链中的下一个机器人(即在登录屏幕后对活动进行操作).例如LoginRobot.clickLogin()返回一个HomeRobot(用于应用程序的主要主屏幕).
我已将断言放在测试中,但如果断言在许多测试中被重用,那么将一些断言抽象到机器人中可能是有意义的.
在某些情况下,使用视图模型对象来保存一组在测试中重用的伪数据可能是有意义的.例如如果测试具有许多测试的许多测试的注册屏幕,那么构建工厂来创建包含名字,姓氏,电子邮件地址等的Registrationviewmodel可能是有意义的,并且在测试中参考它而不是复制那个码.