Espresso 空闲资源

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

确定何时需要空闲资源

Espresso 提供了一套复杂的同步功能。但是,框架的此特性仅适用于在MessageQueue上发布消息的操作,例如View的子类,它正在屏幕上绘制其内容。

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

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

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

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

常见用例

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

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

当这些操作更新您的测试随后验证的 UI 时,注册空闲资源尤其重要。

空闲资源实现示例

以下列表描述了几个空闲资源的示例实现,您可以将其集成到您的应用中

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 的引用,而不是它包含的空闲资源。
  • 维护您用于每个构建变体的空闲资源集合之间的差异。
  • 在应用的服务中定义空闲资源,而不是在引用这些服务的 UI 组件中定义。

将空闲资源集成到您的应用中

尽管您可以通过多种不同的方式将空闲资源添加到应用中,但有一种方法可以特别维护应用的封装,同时仍然允许您指定给定空闲资源表示的特定操作。

在将空闲资源添加到应用中时,我们强烈建议将空闲资源逻辑放在应用本身中,并且仅在测试中执行注册和取消注册操作。

尽管通过采用这种方法,您会在生产代码中创建使用仅测试接口的异常情况,但您可以在已有的代码周围包装空闲资源,从而保持应用的 APK 大小和方法数量。

替代方法

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

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

    注意:尽管此设计决策似乎创建了对空闲资源的自包含引用,但它也破坏了除最简单的应用之外的所有应用的封装。

其他资源

有关在 Android 测试中使用 Espresso 的更多信息,请参阅以下资源。

示例