空闲资源 (Idling resource) 代表一个异步操作,其结果会影响界面测试中的后续操作。通过向 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注解的相应方法中注销此资源。- 使用 空闲注册表 (idling registry) 来注册和注销空闲资源。
通过为您的应用的空闲资源使用此容器,您可以根据需要反复注册和注销空闲资源,并保持一致的行为。
- 仅在空闲资源中维护简单的应用状态。
例如,您实现并注册的空闲资源不应包含对
View对象的引用。
注册空闲资源
Espresso 提供了一个容器类,您可以将应用的空闲资源放入其中。这个类名为 IdlingRegistry,是一个自包含的工件,对您的应用造成的开销极小。该类还允许您采取以下步骤来提高应用的可维护性:
- 在应用的测试中创建对
IdlingRegistry的引用,而不是对其包含的空闲资源的引用。 - 维护您为每个构建变体使用的空闲资源集合之间的差异。
- 在应用的各项服务中定义空闲资源,而不是在引用这些服务的 UI 组件中定义。
将空闲资源集成到您的应用中
虽然可以通过几种不同的方式将空闲资源添加到应用中,但有一种方法在保持应用封装性的同时,仍然允许您指定给定空闲资源所代表的特定操作。
推荐方法
将空闲资源添加到您的应用时,我们强烈建议将空闲资源逻辑放置在应用本身中,仅在测试中执行注册和注销操作。
虽然通过遵循此方法,您会在生产代码中创建使用仅供测试使用的接口这种特殊情况,但您可以将空闲资源包装在您现有的代码周围,从而维持应用的 APK 大小和方法数。
替代方法
如果您不希望在应用的生产代码中包含空闲资源逻辑,还有几种其他可行的集成策略:
- 创建构建变体,例如 Gradle 的 产品风味 (product flavors),并仅在应用的调试构建中使用空闲资源。
- 使用像 Dagger 这样的依赖注入框架,将应用的空闲资源依赖图注入到测试中。如果您使用的是 Dagger 2,则注入本身应源自子组件。
在应用的测试中实现空闲资源,并公开应用中需要在这些测试中进行同步的那部分实现。
警告:尽管此设计决定似乎创建了一个自包含的空闲资源引用,但它除了最简单的应用之外,在所有其他应用中都会破坏封装性。
其他资源
有关在 Android 测试中使用 Espresso 的更多信息,请参考以下资源:
示例
- IdlingResourceSample:与后台作业同步。