Android单元测试(一)

发表于:2017-9-30 13:06

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:yuweiguo    来源:51Testing软件测试网采编

  本文介绍了Android单元测试入门所需了解的内容,包括JUnit、Mockito和PowerMock的使用,怎样写MVP中的P层的单元测试以及RxJava相关测试。
  前言
  最近刚学习了下Android单元测试,看了小创作的中文系列教程,也看了官方的英文教程,刚接触那几天那叫一个痛苦,简直就是陷入了泥潭,接触到了好多框架,并且不清楚什么情况下到底用哪个框架。我们先从最基础的做起,项目目前使用的是MVP架构,先从P层开始做单元测试,由浅入深。之前老说MVP方便单元测试,到底哪方便了,等真开始做单元测试的时候,才真正开始理解MVP架构。首先是我们的P层是不引入任何Android框架代码的(如果引用了Android框架的代码需要引入其它框架才能做测试),之前理解不够,在P层执行Toast(应该在View层执行)操作了。
  我们先来看下需要了解的框架:
  JUnit:Java 编程语言的单元测试框架,主要用于断言。
  Mockito:Java界使用最广泛的一个mock框架,mock表示在测试环境中创建一个类的虚假对象方便用于验证。Mockito不支持mock匿名类、final类、静态方法和private方法。
  PowerMock:扩展了Mockito,支持Mock静态、final、私有方法等。
  JUnit
  仓库地址:https://github.com/junit-team/junit4
  添加依赖:
  testCompile 'junit:junit:4.12'
  Assert类中比较重要的方法如下:
  JUnit 中的注解及含义:
  超时
  Junit 提供了一个指定超时参数。如果一个测试用例执行的毫秒数超过了指定的参数值,那么 Junit 将自动将它标记为失败。
  @Test(timeout=1000)
  public void testPrintMessage() {
      ...
  }
  捕获异常
  Junit 提供了一个捕获异常的参数。你可以测试代码是否抛出了预期的异常。
  参数化
  Junit 4 引入了一个新的功能参数化测试。参数化测试允许开发人员使用不同的值反复运行同一个测试。你可以遵循下面的步骤来创建参数化测试。
  在测试类上添加注解@RunWith(Parameterized.class)。
  创建一个由 @Parameters 注解的公共的静态方法,它返回一个对象的集合(数组)来作为测试数据集合。
  创建一个公共的构造函数,参数个数和类型与提供的数据集合一一对应。
  为每一列测试数据创建一个实例变量。
  用实例变量作为测试数据的来源来创建你的测试用例。
  public class PrimeNumberChecker {
     public Boolean validate(final Integer primeNumber) {
        for (int i = 2; i < (primeNumber / 2); i++) {
           if (primeNumber % i == 0) {
              return false;
           }
        }
        return true;
     }
  }
  import java.util.Arrays;
  import java.util.Collection;
  import org.junit.Test;
  import org.junit.Before;
  import org.junit.runners.Parameterized;
  import org.junit.runners.Parameterized.Parameters;
  import org.junit.runner.RunWith;
  import static org.junit.Assert.assertEquals;
  @RunWith(Parameterized.class)
  public class PrimeNumberCheckerTest {
     private Integer inputNumber;
     private Boolean expectedResult;
     private PrimeNumberChecker primeNumberChecker;
     @Before
     public void initialize() {
        primeNumberChecker = new PrimeNumberChecker();
     }
     // Each parameter should be placed as an argument here
     // Every time runner triggers, it will pass the arguments
     // from parameters we defined in primeNumbers() method
     public PrimeNumberCheckerTest(Integer inputNumber,
        Boolean expectedResult) {
        this.inputNumber = inputNumber;
        this.expectedResult = expectedResult;
     }
     @Parameterized.Parameters
     public static Collection primeNumbers() {
        return Arrays.asList(new Object[][] {
           { 2, true },
           { 6, false },
           { 19, true },
           { 22, false },
           { 23, true }
        });
     }
     // This test will run 4 times since we have 5 parameters defined
     @Test
     public void testPrimeNumberChecker() {
        System.out.println("Parameterized Number is : " + inputNumber);
        assertEquals(expectedResult,
        primeNumberChecker.validate(inputNumber));
     }
  }
  运行结果:
  Mockito
  所谓的mock就是创建一个类的虚假的对象,在测试环境中,用来替换掉真实的对象,以达到两大目的:
  验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
  指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作
  注意:Mockito不支持mock匿名类、final类、静态方法和private方法。
  添加依赖:
   repositories {
     jcenter()
   }
   dependencies {
     testCompile "org.mockito:mockito-core:+"
   }
  验证行为
  //静态导入
   import static org.mockito.Mockito.*;
   //创建mock
   List mockedList = mock(List.class);
   //使用mock对象
   mockedList.add("one");
   mockedList.clear();
   //验证
   verify(mockedList).add("one");
   verify(mockedList).clear();
  stubbing
  //你可以mock一个实体类
  LinkedList mockedList = mock(LinkedList.class);
  //处理指定行为
  when(mockedList.get(0)).thenReturn("first");
  when(mockedList.get(1)).thenThrow(new RuntimeException());
  //将会打印"first"
  System.out.println(mockedList.get(0));
  //将会抛出runtime exception
  System.out.println(mockedList.get(1));
  //将会打印"null" 因为get(999)没有指定行为
  System.out.println(mockedList.get(999));
  //尽管可以验证一个指定行为的调用,但通常这是多余的
  //如果你关心get(0)的返回,那会被其它事情打断
  //如果你不关心get(0)的返回,那你不应该指定行为,直接验证就可以了
  verify(mockedList).get(0);
  默认情况下,所有方法都会有返回值,一个 mock 将返回 null,一个原始/基本类型的包装值或适当的空集。例如,对于一个 int/Integer 就是 0,而对于 boolean/Boolean 就是 false。
  Stubbing 可以被覆盖。
  一旦 stub,该方法将始终返回一个 stub 的值,无论它被调用多少次。
  stubbing 的顺序是重要的。
  参数匹配器
  Mockito 验证参数值使用 Java 方式:通过使用 equals() 方法。有时,当需要额外的灵活性,可以使用参数匹配器:
  //stubbing using built-in anyInt() argument matcher
  when(mockedList.get(anyInt())).thenReturn("element");
  //stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
  when(mockedList.contains(argThat(isValid()))).thenReturn("element");
  //following prints "element"
  System.out.println(mockedList.get(999));
  //you can also verify using an argument matcher
  verify(mockedList).get(anyInt());
  参数匹配器允许灵活的验证或 stubbing。自定义参数的匹配信息,请查看 Javadoc 中 ArgumentMatcher 类。如果你正在使用参数的匹配,所有的参数都由匹配器来提供。
  下面的示例演示验证,但同样适用于 stubbing:
  verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
  //above is correct - eq() is also an argument matcher
  verify(mock).someMethod(anyInt(), anyString(), "third argument");
  //above is incorrect - exception will be thrown because third argument is given without an argument matcher.
  验证调用次数
  //using mock
  mockedList.add("once");
  mockedList.add("twice");
  mockedList.add("twice");
  mockedList.add("three times");
  mockedList.add("three times");
  mockedList.add("three times");
  //following two verifications work exactly the same - times(1) is used by default
  verify(mockedList).add("once");
  verify(mockedList, times(1)).add("once");
  //exact number of invocations verification
  verify(mockedList, times(2)).add("twice");
  verify(mockedList, times(3)).add("three times");
  //verification using never(). never() is an alias to times(0)
  verify(mockedList, never()).add("never happened");
  //verification using atLeast()/atMost()
  verify(mockedList, atLeastOnce()).add("three times");
  verify(mockedList, atLeast(2)).add("five times");
  verify(mockedList, atMost(5)).add("three times");
  times(1) 是默认的,因此,使用的 times(1) 可以显示的省略。
  Stubbing void 方法处理异常
  doThrow(new RuntimeException()).when(mockedList).clear();
  //following throws RuntimeException:
  mockedList.clear();
  有序的验证
  // A. Single mock whose methods must be invoked in a particular order
  List singleMock = mock(List.class);
  //using a single mock
  singleMock.add("was added first");
  singleMock.add("was added second");
  //create an inOrder verifier for a single mock
  InOrder inOrder = inOrder(singleMock);
  //following will make sure that add is first called with "was added first, then with "was added second"
  inOrder.verify(singleMock).add("was added first");
  inOrder.verify(singleMock).add("was added second");
  // B. Multiple mocks that must be used in a particular order
  List firstMock = mock(List.class);
  List secondMock = mock(List.class);
  //using mocks
  firstMock.add("was called first");
  secondMock.add("was called second");
  //create inOrder object passing any mocks that need to be verified in order
  InOrder inOrder = inOrder(firstMock, secondMock);
  //following will make sure that firstMock was called before secondMock
  inOrder.verify(firstMock).add("was called first");
  inOrder.verify(secondMock).add("was called second");
  // Oh, and A + B can be mixed together at will
  有序验证是为了灵活,你不必一个接一个验证所有的交互。
  此外,您还可以通过创建 InOrder 对象传递只与有序验证相关的 mock 。
  验证 mock 上不会发生交互
  //using mocks - only mockOne is interacted
  mockOne.add("one");
  //ordinary verification
  verify(mockOne).add("one");
  //verify that method was never called on a mock
  verify(mockOne, never()).add("two");
  //verify that other mocks were not interacted
  verifyZeroInteractions(mockTwo, mockThree);
  寻找多余的调用
  //using mocks
  mockedList.add("one");
  mockedList.add("two");
  verify(mockedList).add("one");
  //following verification will fail
  verifyNoMoreInteractions(mockedList);
  注意:不建议 verifyNoMoreInteractions() 在每个测试方法中使用。 verifyNoMoreInteractions() 是从交互测试工具包一个方便的断言。只有与它的相关时才使用它。滥用它导致难以维护。
  标准创建 mock 方式——使用 @Mock 注解
  最小化可重用 mock 创建代码
  使测试类更加可读性
  使验证错误更加易读,因为字段名称用于唯一识别 mock
  public class ArticleManagerTest {
   @Mock
   private ArticleCalculator calculator;
   @Mock
   private ArticleDatabase database;
   @Mock
   private UserProvider userProvider;
   private ArticleManager manager;
   @Before
   public void init(){
      MockitoAnnotations.initMocks(this);
   }
   ...
  可以使用内建 runner: MockitoJUnitRunner 或者 rule: MockitoRule
  更多详见 MockitoAnnotations
  另外也可以通过在类上使用@RunWith(MockitoJUnitRunner.class)来达到相同的效果。
  Stubbing 连续调用(迭代器式的 stubbing)
  when(mock.someMethod("some arg"))
  .thenThrow(new RuntimeException())
  .thenReturn("foo");
  //First call: throws runtime exception:
  mock.someMethod("some arg");
  //Second call: prints "foo"
  System.out.println(mock.someMethod("some arg"));
  //Any consecutive call: prints "foo" as well (last stubbing wins).
  System.out.println(mock.someMethod("some arg"));
  下面是一个精简版本:
  when(mock.someMethod("some arg"))
  .thenReturn("one", "two", "three");
  回调 Stubbing
  Mockito允许使用泛型 Answer 接口。我们建议您只使用thenReturn() 或 thenThrow() 来 stubbing 。但是,如果你有一个需要 stub 到泛型 Answer 接口,这里有一个例子:
  when(mock.someMethod(anyString())).thenAnswer(new Answer() {
     Object answer(InvocationOnMock invocation) {
         Object[] args = invocation.getArguments();
         Object mock = invocation.getMock();
         return "called with arguments: " + args;
     }
  });
  //the following prints "called with arguments: foo"
  System.out.println(mock.someMethod("foo"));
  doThrow() 家族方法
  Stubbing void 方法,需要不同的 when(Object)方法,因为编译器不喜欢括号内无效的方法。在用于 Stubbing void 方法中,doThrow(Throwable…) 代替了 stubVoid(Object)。主要原因是提高可读性和与 doAnswer() 保持一致性。
  当你想用 stub void 方法时,使用 doThrow():
  doThrow(new RuntimeException()).when(mockedList).clear();
  //following throws RuntimeException:
  mockedList.clear();
  在调用 when() 的相应地方可以使用 doThrow(), doAnswer(), doNothing(), doReturn() 和 doCallRealMethod(),当stub void 方法和stub 方法在 spy 对象时(见下面),可以不止一次的 stub 相同的方法,在测试的中期来改变 mock 的行为。在所有的 stubbing 调用时,你会更加倾向于使用这些方法来代替 when()。
  spy
  spy可以实现调用对象的默认实现。
  如果不指定mock方法的特定行为,一个mock对象的所有非void方法都将返回默认值:int、long类型方法将返回0,boolean方法将返回false,对象方法将返回null等等;而void方法将什么都不做。
  区别:spy对象的方法默认调用真实的逻辑,mock对象的方法默认什么都不做,或直接返回默认值。
  //假设目标类的实现是这样的
  public class PasswordValidator {
      public boolean verifyPassword(String password) {
          return "xiaochuang_is_handsome".equals(password);
      }
  }
  @Test
  public void testSpy() {
      //跟创建mock类似,只不过调用的是spy方法,而不是mock方法。spy的用法
      PasswordValidator spyValidator = Mockito.spy(PasswordValidator.class);
      //在默认情况下,spy对象会调用这个类的真实逻辑,并返回相应的返回值,这可以对照上面的真实逻辑
      spyValidator.verifyPassword("xiaochuang_is_handsome"); //true
      spyValidator.verifyPassword("xiaochuang_is_not_handsome"); //false
      //spy对象的方法也可以指定特定的行为
      Mockito.when(spyValidator.verifyPassword(anyString())).thenReturn(true);
      //同样的,可以验证spy对象的方法调用情况
      spyValidator.verifyPassword("xiaochuang_is_handsome");
      Mockito.verify(spyValidator).verifyPassword("xiaochuang_is_handsome"); //pass
  }
  捕获参数
  ArgumentCaptor类允许我们在verification期间访问方法的参数。得到方法的参数后我们可以使用它进行测试。
  @Captor
  ArgumentCaptor<List> arguments;
  @Before
  public void init() {
      // Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
      // inject the mocks in the test the initMocks method needs to be called.
      MockitoAnnotations.initMocks(this);
  }
  @Test
  public void testArgument() {
      List asList = Arrays.asList("someElement_test", "someElement");
      final List mockedList = mock(List.class);
      mockedList.addAll(asList);
      verify(mockedList).addAll(arguments.capture());
      final List capturedArgument = arguments.getValue();
      assertEquals(capturedArgument.get(1),"someElement");
  }
  PowerMock
  仓库地址:https://github.com/powermock/powermock
  //添加依赖
  testCompile "org.powermock:powermock-module-junit4:1.6.4"
  testCompile "org.powermock:powermock-module-junit4-rule:1.6.4"
  testCompile "org.powermock:powermock-api-mockito:1.6.4"
  testCompile "org.powermock:powermock-classloading-xstream:1.6.4"
  PowerMock扩展了EasyMock和Mockito框架,增加了对static和final方法mock支持等功能。
  PowerMock有两个重要的注解:
  @RunWith(PowerMockRunner.class)
  @PrepareForTest( { YourClassWithEgStaticMethod.class })
  如果你的测试用例里没有使用注解@PrepareForTest,那么可以不用加注解@RunWith(PowerMockRunner.class),反之亦然。当你需要使用PowerMock强大功能(Mock静态、final、私有方法等)的时候,就需要加注解@PrepareForTest。

21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号