Espresso 空闲资源

空闲资源表示结果会影响界面测试中后续操作的异步操作。通过向 Espresso 注册空闲资源,您可以在测试应用时更可靠地验证这些异步操作。

确定何时需要空闲资源

Espresso 提供了一套先进的同步功能。不过,该框架的这一特性仅适用于在 MessageQueue 上发布消息的操作,如在屏幕上绘制内容的 View 子类。

由于 Espresso 不知道其他任何异步操作(包括在后台线程上运行的异步操作),因此 Espresso 在这些情况下无法提供同步保证。要让 Espresso 知道应用长时间运行的操作,您必须将每项操作都注册为空闲资源。

如果在测试应用异步工作的结果时不使用空闲资源,您可能会发现自己不得不使用以下某种不当的解决方法来提高测试的可靠性:

  • 添加对 Thread.sleep() 的调用。 如果您向测试添加人为延迟,测试套件将需要花费更长的时间才能完成执行,并且有时在较慢的设备上执行时,测试仍可能会失败。此外,这些延迟不能很好地扩展,因为您的应用可能不得不在未来的版本中执行更耗时的异步工作。
  • 实现重试封装容器,使用循环来反复检查您的应用是否仍在执行异步工作,直到发生超时。即使您在测试中指定了最大重试次数,每次重新执行也会消耗系统资源,尤其是 CPU。
  • 使用 CountDownLatch 实例,允许一个或多个线程等待,直到在另一个线程中执行的特定数量的操作完成。这些对象要求您指定超时时长;否则,您的应用可能会被无限期阻止。锁存还会给代码增加不必要的复杂性,因而使维护变得更加困难。

Espresso 可让您从测试中移除这些不可靠的解决方法,而将应用的异步工作注册为空闲资源。

常见用例

在测试中执行类似于以下示例的操作时,应考虑使用空闲资源:

  • 从互联网或本地数据源加载数据
  • 与数据库和回调建立连接
  • 使用系统服务或 IntentService 实例来管理服务
  • 执行复杂的业务逻辑,如位图转换。

当这些操作更新测试随后会验证的界面时,注册空闲资源尤为重要。

空闲资源实现示例

以下列表介绍了可以集成到应用中的空闲资源的几个实现示例:

CountingIdlingResource
维护活动任务的计数器。当计数器为零时,关联的资源被视为空闲。此功能与 Semaphore 的功能非常相似。在大多数情况下,此实现足以在测试期间管理应用的异步工作。
UriIdlingResource
CountingIdlingResource 相似,但计数器必须在特定时间段内为零,才会将相应资源视为空闲。这一额外的等待期间考虑到了连续的网络请求,即线程中的应用可能会在收到对先前请求的响应后立即发出新请求。
IdlingThreadPoolExecutor
ThreadPoolExecutor 的自定义实现,可跟踪创建的线程池中正在运行的任务总数。此类使用 CountingIdlingResource 来维护活动任务的计数器。
IdlingScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor 的自定义实现。它提供的功能与 IdlingThreadPoolExecutor 类相同,但也可跟踪安排在未来执行或安排定期执行的任务。

创建您自己的空闲资源

当您在应用的测试中使用空闲资源时,可能需要提供自定义资源管理或日志记录。在这些情况下,上一部分中列出的实现可能不够用了。如果是这样的话,您可以扩展其中一个空闲资源实现或创建您自己的空闲资源。

如果您实现自己的空闲资源功能,请牢记以下最佳做法,尤其是第一种:

在空闲检查之外调用转换到空闲状态。
应用变为空闲状态后,在 isIdleNow() 的所有实现之外调用 onTransitionToIdle()。这样,Espresso 就不会再进行第二次不必要的检查,以确定给定的空闲资源是否处于空闲状态。

以下代码段说明了此建议:

Kotlin

    fun isIdle() {
        // DON'T call callback.onTransitionToIdle() here!
    }

    fun backgroundWorkDone() {
        // Background work finished.
        callback.onTransitionToIdle() // Good. Tells Espresso that the app is idle.

        // Don't do any post-processing work beyond this point. Espresso now
        // considers your app to be idle and moves on to the next test action.
    }
    

Java

    public void isIdle() {
        // DON'T call callback.onTransitionToIdle() here!
    }

    public void backgroundWorkDone() {
        // Background work finished.
        callback.onTransitionToIdle() // Good. Tells Espresso that the app is idle.

        // Don't do any post-processing work beyond this point. Espresso now
        // considers your app to be idle and moves on to the next test action.
    }
    
在需要之前注册空闲资源。

与空闲资源关联的同步优势只有在 Espresso 首次调用该资源的 isIdleNow() 方法后才会生效。

以下列表显示了此属性的几个示例:

  • 如果您在带有 @Before 注解的方法中注册了空闲资源,则该空闲资源会在每个测试的第一行中生效。
  • 如果您在测试中注册了空闲资源,则该空闲资源会在基于 Espresso 的下一项操作执行期间生效。即使下一项操作与注册该空闲资源的语句在同一测试中,仍会发生此行为。
使用完空闲资源后将其取消注册。

为了节约系统资源,一旦您不再需要空闲资源,应立即将其取消注册。例如,如果您在带有 @Before 注解的方法中注册了空闲资源,则最好在带有 @After 注解的相应方法中取消注册此资源。

使用空闲资源注册表来注册和取消注册空闲资源。

通过将此容器用于应用的空闲资源,您可以根据需要反复注册和取消注册空闲资源,并且仍会观察到一致的行为。

在空闲资源内仅维护简单的应用状态。

例如,您实现和注册的空闲资源不应包含对 View 对象的引用。

注册空闲资源

Espresso 提供了一个容器类,您可以将应用的空闲资源放入其中。此类(名为 IdlingRegistry)是一个独立工件,它给应用带来的开销极少。此类还允许您采取以下措施来提高应用的可维护性:

  • 在应用的测试中,创建对 IdlingRegistry 的引用,而不是对其包含的空闲资源的引用。
  • 保持对每个编译变体使用的空闲资源集合中的差异。
  • 在应用的服务中定义空闲资源,而不是在引用这些服务的界面组件中定义。

将空闲资源集成到应用中

虽然您可以通过几种不同的方式向应用添加空闲资源,但尤其有一种方法可以维护应用的封装,同时仍允许您指定给定空闲资源代表的特定操作。

向应用添加空闲资源时,我们强烈建议您只在测试中执行注册和取消注册操作,而将空闲资源逻辑本身放在应用的生产代码中。

虽然遵循这种方法会导致出现在生产代码中使用仅供测试之用的接口这一异常情况,但您可以将空闲资源封装在已有代码周围,从而保持应用的 APK 大小和方法计数。

替代方法

如果您不希望应用的生产代码中存在空闲资源逻辑,还有其他几种可行的集成策略:

  • 创建编译变体(如 Gradle 的产品特性),并且仅在应用的调试编译中使用空闲资源。
  • 使用诸如 Dagger 之类的依赖注入框架将应用的空闲资源依赖关系图注入到测试中。如果您使用的是 Dagger 2,则注入本身应源自子组件。
  • 在应用的测试中实现空闲资源,并公开需要在这些测试中同步的应用实现部分。

    注意:虽然这一设计决策似乎创建了对空闲资源的独立引用,但它也破坏了除最简单的应用之外的所有应用的封装。

其他资源

如需详细了解如何在 Android 测试中使用 Espresso,请参阅以下资源。

示例