一、什么是单元测试
单元测试是测试某个类的某个方法能否正常工作的一种手段。
二、单元测试目的
- 验收(改动和重构)
- 快速验证逻辑
- 优化代码设计
三、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);
}
}
新建单元测试:
运行:
运行结果
(验证通过):
(验证失败):
查看覆盖率:
五、单测基本规范与验收标准
代码规范:
- 测试类命名:被测类名+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 |
与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 |
创建自定义的参数匹配模式 |
其他方法
方法名 | 方法描述 |
---|---|
reset(T … mocks) | 重置Mock |
spy(Class |
实现调用真实对象的实现 |
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();
...
}