Android 中单元测试并不常见, 这篇文章就我自己的知识范围来介绍:

JUnit

在学习 Java 时就知道这是一个用来给纯 Java 测试的工具, 在 Android 中一样使用, Android Studio 可以用快捷键 cmd + enter 为每个类自动创建测试类. 在测试类中, 一般使用 Assert 来断言每个条件的对错, 而 JUnit 的注解则为单元测试提供框架.

  • @Test

    定义测试单元(每个方法为一个单元用例), 接受参数有

  • expected 预期会抛出某个异常, 不抛出则报错

    1
    Class<? extends Throwable> expected() default None.class;
  • timeout 超时

    1
    long timeout() default 0L;
  • @Before

    每个 Test 方法执行之前都会调用, 可以做预处理操作, 必须修饰 public 方法

  • @After

    每个 Test 方法执行之后都会调用, 可以做清理操作

  • @BeforeClass

    由于连续测试可能需要共享一个变量, 或者每个测试单元执行前都需要很长时间的准备工作, 可以把这些准备工作移到 BeforeClass 中, 该注解必须注解于一个 public static void 没有参数 的方法.

  • @AfterClass

    用于 @BeforeClass 的清理工作

  • @Ignore

    用于执行测试的时候忽略某个 @Test 单元

  • @Rule

    定义 @Test 单元执行时的逻辑, org.junit.rules 包定义了一些常用的 Rule 规则, 但我们也可以自定义 Rule.

    1
    2
    3
    4
    5
    6
    7
    8
    public class RuleSample implements TestRule {

    @Override
    public Statement apply(Statement base, Description description) {
    base.evaluate();
    return null;
    }
    }

    Mockito

    Mock 即[模拟]的意思, 当某些类因为依赖太多等关系难以创造, 或者我们只需要一个类的对象时, Mock 便是一个很好的工具, 可以帮助我们隔离代码进行测试. 而 Mockito 便是一个 Android 常用的 Mock 工具类.

    模拟对象

    Mockito 支持多种模拟对象方法.

  • @Mock 直接注解对象. 注解对象需要初始化, 可以有四种初始化方法.

  • @Before 注解的初始化方法中使用 MockitoAnnotations.initMocks(this);

  • 使用自带的 @Rule 初始化

    1
    2
    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();
  • 使用 @RunWith 注解测试类.

    1
    2
    3
    4
    @RunWith(MockitoJUnitRunner.class)
    public class Test {

    }
  • 使用 Mockito.mock() 初始化对象.

  • @InjectMock 创建一个实例, 其余用 @Mock @Spy 注解创建的 mock 对象将被注入到用该实例中.

    设置桩

    Mockito 支持以下方法设置桩

    1
    2
    Mockito.doXXX().when(XXX)
    Mockito.when(XXX).thenXXX()

    举个例子, 如果希望 TextView 在调用 getText() 时返回特定的内容, 可以使用

    1
    2
    Mockito.when(mTextView.getText()).thenReturn("123);
    Mockito.doReturn("123").when(mTextView).getText();

    桩可以设置多次, 最终只会返回最后一次设置的值.

    验证

    Mockito 支持以下方法来验证函数执行

    1
    2
    public static <T> T verify(T mock)
    public static <T> T verify(T mock, VerificationMode mode)

    默认的 VerificationMode 即 times(1) , 即方法执行了一次, sdk 也提供了几种默认的验证模式实现, 如 never() , atLeastOnce() 等.

    参数匹配

    为了模拟某些参数的输入, 可以匹配这些参数的输入, 并返回所需的值. ArgumentMatchers.any() 是一个比较常用的方法.

    比如模拟 TextView 的 OnClickListener().

    1
    2
    3
    4
    5
    6
    7
    8
    Mockito.doAnswer(new Answer() {
    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
    View.OnClickListener listener = invocation.getArgument(0);
    listener.onClick(null);
    return listener;
    }
    }).when(mTextView).setOnClickListener(ArgumentMatchers.any(View.OnClickListener.class));

    @Spy

    Mockito.spy() 返回的对象, 除非该方法已经有设置桩, 否则会调用该对象的真实方法. 可以用来改变对象特定方法的返回值, 而不改变对象本身. 注意 spy 不能 mock final 方法.

    1
    2
    3
    4
    5
    6
    List list = new LinkedList();
    List spy = Mockito.spy(list);
    spy.add("one");
    when(spy.size()).thenReturn(100);
    spy.add("two");
    System.out.println(spy.size());

    JMockit

    Mockito 的语法虽然比较简单易懂, 但它支持的功能还是不够多, 有一部分人是配合 PowerMock 一起使用, 但还有一个更强大的 Mock 工具值得使用.

    模拟对象

    注意 @RunWith 注解初始化测试类.

  • @Mocked 注解对象, 除了基本类型和数组对象, 其余所有都可以 mock, 会 mock 类中全部方法及其父类. 可以指定 stubOutClassInitialization 来决定 mock 对象时是否需要初始化静态变量, 某些 JNI 调用的静态变量在 mock 时初始化可能为 false, 这时候可以指定为 true 来跳过初始化.

  • @Injectable 仅 mock 指定的对象, 对于可以传入的对象, 使用 @Injectable 比 @Mock 更好

  • @Capturing mock 类及其子类, 也可以 mock 接口, 可以指定 maxInstances 来决定需要 mock 多少个对象.

    常规使用

    JMockit 用法是 录制 - 执行 - 验证 (record - replay - verify).

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Test
    public void testMethod() throws Exception {

    //record
    new Expectations(){{
    //执行方法, 可以是构造函数
    a.getName();
    //指定返回
    result = "123";
    }};

    //run test code

    //verify
    new Verifications(){{

    }};
    }

    参数匹配

    参数匹配有很多, 最宽松的是 anyXXX , 其次是 withXXX.
    使用 null 时, 必须有一个 anyXXX 或者 withXXX .

    MockUp API

    JMockit 我觉得 MockUp Api 特别强大, 几乎可以 Mock 所有的方法, 静态方法, 构造函数, 私有函数都可以, 这也是这个工具强大的地方.

    比如有如下测试类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Test {

    public Test(String name) {

    }

    public static String getName() {

    }

    private String getAnswer() {

    }
    }

    那么可以通过这样去 mock 对应的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    new MockUp<Test>() {

    @Mock
    public void $init(Invocation invocation, String name) {
    Test test = invocation.getInvokedInstance();
    }

    @Mock
    public String getName() {
    return "Mocked Name";
    }

    @Mock
    private String getAnswer() {
    return "Mocked Answer";
    }
    }

    一些 Tip

  •