AndroidTDD

—— 安卓测试驱动开发/安卓测试验证

欢马劈雪     最近更新时间:2020-08-04 05:37:59

86

安卓测试主要包括两种:单元测试;集成测试;测试主要通过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)
  • 单元测试的目标
    • 在非安卓系统组件相关的代码,直接每个方法进行测试,很好理解
    • 系统组件相关的代码,主要测:试依赖于生命周期函数的逻辑;由简单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测例。

集成测试

展开阅读全文