Android单元测试(一)-基础

一、什么是单元测试

单元测试是测试某个类的某个方法能否正常工作的一种手段。

二、单元测试目的

  • 验收(改动和重构)
  • 快速验证逻辑
  • 优化代码设计

三、Android单元测试工具链

google推荐:junit4+mockito+rebolectric
最佳实战选择:junit4+mockito+powermock+rebolectric

工具介绍:

  • Junit4 Java单元测试框架。
  • Mockito 针对Java的mocking框架,解决测试类对其他类的依赖问题。
  • Powermock mockito功能的延伸,且完美兼容mockito。
  • Rebolectric 模拟android接口,让单测跑在jvm上,去掉对android环境的依赖。

注:
mockito功能有点弱,很多功能受限。powermock可以跟mockito兼容而且功能更强。与mockito比,支持修改和mock静态类或对象的私有方法/成员,还支持很多反射方法,非常实用。

四、如何在Android项目中运行单元测试

案例:

public class SimpleClass {
   public int add(int a, int b) {
       return a + b;
   }
}

public class SimpleClassTest {
   @Test
   public void testAdd() {
       SimpleClass simpleClass = new SimpleClass();
       int sum = simpleClass.add(1, 2);
       Assert.assertEquals(3, sum);
   }
}

新建单元测试:

运行:

Android单元测试(一)-基础_第1张图片

运行结果

(验证通过):

(验证失败):

查看覆盖率:

五、单测基本规范与验收标准

代码规范:

  • 测试类命名:被测类名+Test;
  • 测试类被测方法命名:test+首字母大写的被测方法名,如果一个被测方法有多个测试用例,则在其后添加“_+序号”;
  • 测试类必现有setup()、cleanup()方法,用于初始化和释放资源;
  • 测试用例必现有断言。

验收标准:

  • 代码覆盖率:行覆盖,函数覆盖,分支覆盖。目前google、facebook等国外一线大厂单测覆盖率要求是82%左右。

六、单测框架及其使用

6.1 JUnit4

JUnit4 :控制执行顺序 + 验证结果

控制执行顺序:

注解名 含义
@Test 表示此方法为测试方法
@before 每个测试方法前执行,可做初始化操作
@After 在每个测试方法后执行,可做释放资源操作
@Ignore 忽略的测试方法
@BeforeClass 在类中所有方法前运行。此注解修饰的方法必须是static void
@AfterClass 在类中最后运行。此注解修饰的方法必须是static void
@RunWith 指定该测试类使用某个运行器
@Parameters 指定测试类的测试数据集合
@Rule 重新制定测试类中方法的行为
@FixMethodOrder 指定测试类中方法的执行顺序

验证结果:

1)Assert

方法名 方法描述
assertNotEquals 断言传入的预期值与实际值是不相等的
assertNotEquals 断言传入的预期值与实际值是不相等的
assertArrayEquals 断言传入的预期数组与实际数组是相等的
assertNull 断言传入的对象是为空
assertNotNull 断言传入的对象是不为空
assertTrue 断言条件为真
assertFalse 断言条件为假
assertSame 断言两个对象引用同一个对象,相当于“==”
assertNotSame 断言两个对象引用不同的对象,相当于“!=”
assertThat 断言实际值是否满足指定的条件

2)Assert.assertThat 作用跟Assert类似。

匹配器 说明 例子
is 断言参数等于后面给出的匹配表达式
not 断言参数不等于后面给出的匹配表达式
equalTo 断言参数相等
equalToIgnoringCase 断言字符串相等忽略大小写
containsString 断言字符串包含某字符串
startsWith 断言字符串以某字符串开始
endsWith 断言字符串以某字符串结束
nullValue 断言参数的值为null
notNullValue 断言参数的值不为null
greaterThan 断言参数大于
lessThan 断言参数小于
greaterThanOrEqualTo 断言参数大于等于
lessThanOrEqualTo 断言参数小于等于
closeTo 断言浮点型数在某一范围内
allOf 断言符合所有条件,相当于&&
anyOf 断言符合某一条件,相当于或
hasKey 断言Map集合含有此键
hasValue 断言Map集合含有此值
hasItem 断言迭代对象含有此元素

举例:

public class Junit4Example {
   public int plus(int a, int b) {
       return a + b;
   }
}

public class Junit4ExampleTest {
   @BeforeClass
   public static void beforeClass() throws Exception {
   }

   @Before
   public void setUp() throws Exception {
   }

    //两种验证写法:
    //Assert
   @Test
   public void testPlus_1() {
       Junit4Example junit4Example = new Junit4Example();
       Assert.assertEquals(3, junit4Example.plus(1, 2));
   }

    //assertThat
   @Test
   public void testPlus_2() {
       Junit4Example junit4Example = new Junit4Example();
       Assert.assertThat(3, equalTo(junit4Example.plus(1, 2)));
   }

   @After
   public void tearDown() throws Exception {
   }

   @AfterClass
   public static void afterClass() throws Exception {
   }
}

生命周期:

com.stan.androidtest.Junit4ExampleTest beforeClass
com.stan.androidtest.Junit4ExampleTest setUp
com.stan.androidtest.Junit4ExampleTest testPlus_1
com.stan.androidtest.Junit4ExampleTest tearDown
com.stan.androidtest.Junit4ExampleTest setUp
com.stan.androidtest.Junit4ExampleTest testPlus_2
com.stan.androidtest.Junit4ExampleTest tearDown
com.stan.androidtest.Junit4ExampleTest afterClass
6.2 Mockito

Mock的作用:解决测试类对其他类的依赖问题。Mock的类所有方法都是空,所有变量都是初始值。
Mockito 使用操作主要分如下三步:
mock/spy对象 + 打桩 + 验证行为

mock/spy对象
区别:
mock: 所有方法都是空方法,非void方法都将返回默认值,比如int方法返回0,对象方法将返回null,而void方法将什么都不做。
spy:跟正常类对象一样,是正常对象的替身。

适用场景
mock:类对外部依赖较多,只关新少数函数的具体实现;
spy:跟mock相反,类对外依赖较少,关心大部分函数的具体实现。

打桩

方法名 方法描述
thenReturn(T value) 设置要返回的值
thenThrow(Throwable… throwables) 设置要抛出的异常
thenAnswer(Answer answer) 对结果进行拦截
doReturn(Object toBeReturned) 提前设置要返回的值
doThrow(Throwable… toBeThrown) 提前设置要抛出的异常
doAnswer(Answer answer) 提前对结果进行拦截
doCallRealMethod() 调用某一个方法的真实实现
doNothing() 设置void方法什么也不做

验证行为

方法名 方法描述
after(long millis) 在给定的时间后进行验证
timeout(long millis) 验证方法执行是否超时
atLeast(int minNumberOfInvocations) 至少进行n次验证
atMost(int maxNumberOfInvocations) 至多进行n次验证
description(String description) 验证失败时输出的内容
times(int wantedNumberOfInvocations) 验证调用方法的次数
never() 验证交互没有发生,相当于times(0)
only() 验证方法只被调用一次,相当于times(1)

参数匹配

方法名 方法描述
anyObject() 匹配任何对象
any(Class type) 与anyObject()一样
any() 与anyObject()一样
anyBoolean() 匹配任何boolean和非空Boolean
anyByte() 匹配任何byte和非空Byte
anyCollection() 匹配任何非空Collection
anyDouble() 匹配任何double和非空Double
anyFloat() 匹配任何float和非空Float
anyInt() 匹配任何int和非空Integer
anyList() 匹配任何非空List
anyLong() 匹配任何long和非空Long
anyMap() 匹配任何非空Map
anyString() 匹配任何非空String
contains(String substring) 参数包含给定的substring字符串
argThat(ArgumentMatcher matcher) 创建自定义的参数匹配模式

其他方法

方法名 方法描述
reset(T … mocks) 重置Mock
spy(Class classToSpy) 实现调用真实对象的实现
inOrder(Object… mocks) 验证执行顺序
@InjectMocks注解 自动将模拟对象注入到被测试对象中

Mockito局限性:不支持private方法、匿名类、final类、static方法。

举例:

Mockito 常规操作:mock/spy对象 + 打桩 + 验证行为

mock对象 + 打桩配合:

mock + doCallRealMethod()

//mock当前类的空实现

MockitoExample example = Mockito.mock(MockitoExample.class);

//让类的空方法恢复成真实实现

Mockito.doCallRealMethod().when(example).plus(Mockito.anyInt(), Mockito.anyInt());

spy + doNothing()/doReturn()

//spy当前类的真实实现

MockitoExample example = Mockito.spy(MockitoExample.class);

//让某些方法不执行真实逻辑

//有返回值的

Mockito.doReturn(0).when(example).function(Mockito.anyInt());

//没返回值的

Mockito.doNothing().when(example).function(Mockito.anyInt());

验证行为:

//验证方法被调用

Mockito.verify(example, Mockito.times(1)).function(1);

//验证方法执行结果

Assert.assertEquals(3, example.function(1, 2));

特殊验证:

//一个参数确定,另一个是匹配器

Mockito.verify(example, Mockito.times(1)).function(Mockito.eq(1),Mockito.anyInt());

这里确定参数不能直接写,需要eq包一下。

//匹配非基础类型

Mockito.verify(example, Mockito.times(1)).function(Mockito.any(类名.class));
6.3 PowerMockito

PowerMock是Mockito的扩展增强版,支持mock private、static、final方法和类,还增加了很多反射方法可以方便修改静态和非静态成员等。功能比Mockito增加很多。

@RunWith(PowerMockRunner.class)//使用PowerMockRunner时是兼容Mockito的,Mockito中的各种方法也是可以正常使用到
@PrepareForTest()//要mock的类需要写在这个注解里
public class PowerMockitoExampleTest {
    @Test
   public void testGetPrivateField() {
   ...
   }
}

针对对象操作

功能 实现
读取对象私有成员 WhiteBox.getInternalState
修改对象私有成员 WhiteBox.setInternalState 或 MemberModifier.field
verify对象私有方法 PowerMockito.verifyPrivate
Invoke对象私有方法 Whitebox.invokeMethod
修改对象私有函数 PowerMockito.replace
调用私有构造方法 Whitebox.invokeConstructor
跳过方法执行 PowerMockito.suppress

针对静态类

功能 实现
读取对象私有成员 WhiteBox.getInternalState
修改对象私有成员 WhiteBox.setInternalState
调用静态私有方法 Whitebox.invokeMethod
替换静态函数 PowerMockito.replace
verify公有静态方法 PowerMockito.verifyStatic
verify私有静态方法 PowerMockito.verifyPrivate

针对final类

mock final类举例:

public final class FinalClassA {
   public final String finalMethod() {
       return "finalMethod";
   }
}

@Test
public final void mockPowerFinalClassTest() {
       FinalClassA instance = new FinalClassA();
       FinalClassA mock = PowerMockito.mock(FinalClassA.class);
       Mockito.when(mock.finalMethod()).thenReturn("mock method");
       Assert.assertNotEquals(instance.finalMethod(), mock.finalMethod());
}

当然PowerMockito也不是万能的,它目前解决不了匿名类的场景。

七、测试框架-Robolectric

单测运行在真机或模拟器上,跑起来非常耗时,而且依赖于Android环境不是很方便。如果直接运行在JVM就方便很多,但是直接运行在JVM,代码依赖的Android SDK的api 这些接口在android.jar中,获取不到就会抛RuntimeException(“stub!!”)。

Robolectric作为一个测试框架,通过实现一套JVM能运行的Android代码,然后在unit test运行的时候去截取android相关的代码调用,然后转到他们的他们实现的代码去执行这个调用的过程。解决了上面的问题。

@RunWith(RobolectricTestRunner.class)  //Robolectric需要使用RobolectricTestRunner这个runner
@Config(constants = BuildConfig.class, application = ApplicationStub.class, sdk = 21)
@PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" }) //PowerMock有自己的ClassLoader,叫做MockClassLoader,Robolectric也有自己的ClassLoader,SandBoxClassLoader,如果一个类被两个加载了,使用时会报错。这里对一些类不要PowerMock,避免冲突
@PrepareForTest(StaticClass.class)
public class MyTest {
   //这个rule一定要有,解决powermock和robolectric兼容性
   @Rule
   public PowerMockRule rule = new PowerMockRule();
...
}

你可能感兴趣的:(Android单元测试(一)-基础)