【问题标题】:How to implement Jake Wharton's robot pattern to Espresso UI testing?如何在 Espresso UI 测试中实现 Jake Wharton 的机器人模式?
【发布时间】:2017-10-25 08:45:12
【问题描述】:

Jake Wharton 发表了一场引人入胜的演讲,他提出了一些巧妙的方法来改进我们的 UI 测试,方法是从测试中抽象出我们如何执行 UI 的细节:https://news.realm.io/news/kau-jake-wharton-testing-robots/

他给出的一个示例是一个如下所示的测试,其中 PaymentRobot 对象包含付款金额和收款人如何输入 UI 的详细信息。把那个地方放在一个地方很有意义,所以当 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 的概述演讲之外,我找不到任何实施示例。

【问题讨论】:

    标签: android android-espresso android-testing


    【解决方案1】:

    我完全误解了 Espresso 的工作原理,这导致我更加困惑如何将其应用于页面对象模式。我现在看到 Espresso 不需要任何类型的对被测活动的引用,而只是在 runner 规则的上下文中运行。对于其他苦苦挣扎的人,这里有一个充实的示例,将机器人/页面对象模式应用于登录屏幕上的验证测试,其中包含用户名和密码字段,我们正在测试当任一字段为时是否显示错误消息空:

    LoginRobot.java(抽象自动化登录活动)

    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 并在测试中引用它而不是复制它可能是有意义的代码。

    【讨论】:

    • LoginPage 在 LoginValidationTests.java 文件中假设为 LoginRobot?
    • 是的,Robots 本质上与页面对象模型相同。 Jake 做了一些很棒的修改,让 Kotlin 更好一些,并使用机器人代替 Selenium 中常见的 Screens 或 PageObjects。
    【解决方案2】:

    机器人如何与 Activity/Fragment 交互,以及 具体在哪里实例化?我希望这发生在 赛跑者的测试,但他的例子似乎表明并非如此。 该方法看起来可能非常有用,但我不明白如何 在实践中实现它,无论是针对单个 Activity/Fragment,还是 对于它们的序列。

    Robot 应该是用于测试目的的实用程序类。它不应该是包含在您的Fragment/Activity 或您想使用的任何内容中的生产代码。杰克的比喻非常完美。 Robot 就像一个人与应用程序的屏幕进行交互。因此,Robot 的公开 api 应该是特定于屏幕的,无论您的实现是什么。它可以跨越多个activitiesfragmentsdialogs 等,也可以反映与单个组件的交互。这实际上取决于您拥有的应用程序和测试用例。


    如何扩展这种方法,以便 isSuccess() 方法可以 处理各种场景。例如如果我们正在测试登录屏幕,如何 isSuccess() 可以处理各种预期结果,例如:身份验证 成功、API 网络失败和身份验证失败(例如 403 服务器 回复)?理想情况下,API 会在 Retrofit 后面被嘲笑,并且每个 通过端到端 UI 测试测试的结果。

    Robot 的 API 确实应该在您的测试中指定 what。不是如何。因此,从使用Robot 的角度来看,它不会关心您是否遇到API 网络故障身份验证故障。这是方法。 “你是如何以失败告终的”。人类 QA 测试人员 (=== Robot) 不会查看 http 流来注意到 api 失败或 http 超时之间的区别。他/她只会看到您的屏幕显示FailureRobot 只关心是Success 还是Failure

    您可能要在此处测试的另一件事是您的应用是否显示一条消息,通知用户连接错误(无论确切原因如何)。

    class ResultRobot { 
        ResultRobot isSuccess() { ... }
        ResultRobot isFailure() { ... }
        ResultRobot signalsConnectionError() { ... }
    }
    

    result.isFailure().signalsConnectionError();
    

    【讨论】:

    • 在这种情况下,如果他们所做的只是测试一个为 UI 流建模的实用程序类,那么测试的目的是什么?我很难看出它有什么价值,因为我认为页面对象的目的是作为对 Activity 或 Fragment 进行操作的接口。
    • 您需要记住,这种类型的测试不一定是您拥有的唯一类型。将那些“机器人”UI 测试想象为抽象级别的最高级别(如果您考虑您的 UI 测试)。真正将他们视为雇用 QA 测试人员的替代品。测试 http 超时是否准确地导致向用户显示“消息通知 [...] 连接错误”可以是对较低抽象级别的测试。换句话说:即使您聘请了 QA 测试人员,您仍然会对其进行测试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-31
    • 2013-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多