空闲资源表示异步操作,其结果会影响界面测试中的后续操作。通过向 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
的引用,而不是对其包含的空闲资源的引用。 - 确保用于每个 build 变体的空闲资源集合中的差异。
- 在应用的服务(而不是引用这些服务的界面组件)中定义空闲资源。
将空闲资源集成到应用中
虽然您可以通过多种不同的方式向应用添加空闲资源,但尤其是一种方法,该方法会维护应用的封装,同时仍允许您指定给定空闲资源所代表的特定操作。
建议的方法
向应用添加空闲资源时,我们强烈建议将空闲资源逻辑放置在应用本身中,并且仅在测试中执行注册和取消注册操作。
尽管按照此方法会造成在生产代码中使用仅供测试用的接口这一异常情况,但您也可以将空闲资源封装在已有代码周围,从而保持应用的 APK 大小和方法计数。
替代方法
如果您不希望在应用的生产代码中使用空闲资源逻辑,还有其他几种可行的集成策略:
- 创建 build 变体(如 Gradle 的产品变种),并且仅在应用的调试 build 中使用空闲资源。
- 使用诸如 Dagger 之类的依赖项注入框架将应用的空闲资源依赖关系图注入到测试中。如果您使用的是 Dagger 2,则注入本身应源自子组件。
在应用的测试中实现空闲资源,并公开需要在这些测试中同步的应用实现部分。
注意 :虽然这一设计决策似乎创建了对空闲资源的独立引用,但它也破坏了除最简单的应用之外的所有应用的封装。
其他资源
如需详细了解如何在 Android 测试中使用 Espresso,请参阅以下资源。
示例
- IdlingResourceSample:与后台作业同步。