安卓测试主要包括两种:单元测试;集成测试;测试主要通过mock依赖,验证行为。 Mock框架、单元测试框架、集成测试框架是TDD所需的主要工具。
Mock框架:Mockito
- stubbed的方法没有必要verify
- 当被测代码对返回值不关心时,不要stub
- mock对象的方法默认将返回空值(null,空集合,默认基本类型值)
- stub可以被重载,但当出现这种需求时,就说明stub已经太多了,需要改进设计
- stub的顺序有影响,但不应依赖其顺序的影响
- 默认使用Java原生equals进行判断,也支持ArgumentMatcher,有内置, 也支持自定义/Hamcrest,但最后者不建议使用(影响可读性)
- 一旦使用了ArgumentMatcher,则所有的参数都要使用ArgumentMatcher
times(int)
,never()
,atLeastOnce()
,atLeast(int)
,atMost(int)
验证调用次数,verify默认的是times(1)
,因此无需显式指定- 验证无返回值函数抛出异常:
doThrow(new RuntimeException()).when(mockedList).clear();
InOrder
可以验证调用的顺序(不同语句),原则上只需要验证关键逻辑,没必要 所有调用都需要验证、甚至其顺序only()
的语义:仅有该方法被调用,且仅被调用一次never()
的语义:该方法未被调用过verifyZeroInteractions(...)
的语义:mock对象没有任何方法调用verifyNoMoreInteractions(...)
:验证没有其他的调用(除了此前验证的方法)- 对多次调用进行stub,最后一次将一直有效
- doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() family of methods
- spy,对真实对象进行spy,部分mock。但是spy对象的操作和原有真实对象的操作是独立的
- Capturing arguments for further assertions,在verify中可以捕获调用的参数,后续进行验证
- reset mock,不建议使用
- BDD,given, when, then
- mock对象可以序列化
- Verification with timeout
- (new) Better generic support with deep stubs (Since 1.10.0)
- How to mock dependencies in Unit, Integration and Functional tests; Dagger, Robolectric and Instrumentation
- Testing made sweet with a Mockito
Assertion框架
- Google出品的Truth, 及其使用介绍blog
单元测试:The Square Way
- 单元测试是方法级别的测试,需要测试的是一个类的公开接口/方法,测试其逻辑正确性, 如果发现一个类的某个方法所使用的依赖难以mock,it's a smell,重构吧
- 基础理论部分
- The three steps of a unit test: arrange, act, and assert
- check the return value of the method
- get a reference to some publicly accessible property of the object being tested
- check the state of the object’s injected dependencies
- 所以编码时,应该尽量将代码逻辑的依赖通过注入的方式(或提供public setter, 或将被测函数的依赖作为参数传入)提供,以便于测试
- post-act-state的验证要注意单元测试与集成测试的区别:单元测试只验证 一个方法的逻辑正确性,不验证多个方法(模块)一起工作的逻辑正确性;
- static方法对测试非常不利,因为其对象依赖、方法依赖无法mock, 而其post-act-state也通常无法assert,开发过程中应尽量避免
- 实践部分
架构图
Remove all business logic from app component classes (e.g., Activitys, Fragments, Services) and place that logic into "business objects", POJO objects whose dependencies are injected, android-specific implementations of android-agnostic interfaces.
Delegate all application specific behavior to POJO objects whose dependencies are Android-specific implementations of Android-agnostic interfaces.- Non-UI App Components
- 把业务逻辑(比如根据数据类型执行不同操作、检查数据合法性等)从组件类中 抽离出来,业务逻辑类对于组件类的依赖也不要直接使用,而是通过定义接口来 进行转发调用,这样就能使得业务逻辑类与SDK解耦。
- 业务逻辑类的依赖通过依赖注入框架注入,便于测试时mock。
- 而剩下的组件类工作简单,只负责转发业务逻辑类的功能请求,就没必要进行测试了。
- UI App Component Classes
- 通过MVP模式,将UI组件类和业务逻辑类解耦,同时移除对SDK组件类的依赖;
- pre-act-state,post-act-state,测试对象的依赖中,如果mock框架 (如Mockito)可以mock,OK;如果不能mock(例如Activity,BroadcastReceiver) ,则可以通过定义接口的形式替换这些依赖,而接口的实现则是简单直接的转发,无需测试; pre-act-state便可以设置完毕,调用被测函数后,验证post-act-state即可。
- Dependency Inject
- 可以使用Constructor inject的类就不要使用Field inject。前者更利于单元测试。 其实除了SDK组件类,其他的类基本上都可以使用Constructor inject。
- 无需依赖第三方框架(Robolectric,Dagger)
- Non-UI App Components
- 单元测试的目标
- 在非安卓系统组件相关的代码,直接每个方法进行测试,很好理解
- 系统组件相关的代码,主要测:试依赖于生命周期函数的逻辑;由简单UI交互引发的逻辑; 自定义接口的逻辑(如MVP中的View);
-
与其他框架/工具的整合
-
RxJava
- Rx为异步而生,但测试往往需要同步进行验证
- 可通过
myObservable.toBlocking().first();
进行同步 - 也可通过官方的
TestSubscriber
,该类提供了众多实用的方法,例如同步获取 Observable发射的所有对象,断言没有onError,等待onComplete等 -
处理生产代码中的异步问题,可以在测试时hook
Schedulers.io()
为Schedulers.immediate()
,但在hook之前需要reset RxJavaPlugins, 在RxJava中这个方法是package private,所以做到这点有点hack,在RxAndroid中则 无需如此麻烦,因为或者RxAndroid提供了很好的hook支持。package rx.plugins; public class RxJavaTestPlugins extends RxJavaPlugins { RxJavaTestPlugins() { super(); } public static void resetPlugins(){ getInstance().reset(); } } ... RxJavaTestPlugins.resetPlugins(); RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook() { @Override public Scheduler getIOScheduler() { return Schedulers.immediate(); } });
另外有一点需要指出的是,要想RxJava的hook起作用,必须要在
Schedulers
类初始化之前进行hook,那么在测试的时候,只能通过实现自定义的TestRunner来做到了 ,在TestRunner的构造函数中进行reset和hook就OK了。而RxAndroid的hook则没这 么麻烦,在测例的setUp函数中进行就OK。 - RxJava还能设置
ObservableExecutionHook
,示例:RxJavaPlugins.getInstance().registerObservableExecutionHook(new DebugHook(new DebugNotificationListener() {...}
- 网络库:Retrofit,OkHttp
- Retrofit提供了retrofit-mock模块,用于测试retrofit,详情可见 retrofit-mock测例。
- 对于2.0中的adapter-rxjava,retrofit也提供了adapter-rxjava-mock模块, 详情可见adapter-rxjava-mock测例。
- 另外,OkHttp也提供了MockWebServer,也可以利用起来,详见 converter-gson测例。
-