Android 单元测试默认使用 JUnit4,和其他的 Java 项目使用的测试方案区别不大,这里就对 JUnit4 的使用要点进行总结,详细内容应查询官方培训的测试教程。
JUnit4 的测试代码一般都放在 src/main/test
目录下,在 Android 项目中负责进行本地单元测试。
依赖关系的设置代码为(版本号应参考最新版本说明):
1
| testCompile 'juint:junit:4.12'
|
最简单的测试方法就是最任意有测试内容的方法标记注解 @Test
,而后 IDE 会自动识别测试方法。测试方法内容编写和普通方法并无不同,只是其中有一系列的 assert 开头的方法,用来判断测试结果。比如:
1
2
3
4
5
6
7
8
| public class EmailValidatorTest {
@Test
public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
assertThat(EmailValidator.isValidEmail("name@email.com"), is(true));
}
...
}
|
Hamcrest 是一个通用的匹配器构造和检测库,有多个编程语言的版本,Java 版的官网地址为:http://hamcrest.org/JavaHamcrest/
Android Studio 会自动为 Android 项目添加这个库的依赖,无需手动管理。一般使用这个库,是为了扩展断言的用法,同时也能提供可读性更高的断言语句。比如:
1
2
3
| ClassA a = new ClassA();
ClassA b = new ClassA();
assertThat(a, equalTo(b));
|
一般常使用 assertThat
方法进行断言,第一个参数为需要断言的结果,第二个参数为断言对应的匹配器或匹配模式。
常用的匹配器有:
- anything:无论结果如何,总是匹配
- equalTo:相等(使用
Object.equals
判断) - is:equalTo 的语法糖
- not:不匹配,内部可包裹匹配器,相当与逻辑符
!
- allOf:必须全部匹配参数中的匹配器,最后结果才能匹配成功
- anyOf:任一参数匹配即可匹配成功
- notNullValue:结果非空
- nullValue:结果为 null
其他的匹配器用法可以参考官方文档
熟练的使用匹配器可以大幅减少测试代码的编写,尤其是复杂的逻辑代码和结果判断代码,也防止了测试代码本身出错的情况。
如果测试中需要依赖某些测试对象之外的代码,尤其一些简单的 Android API,那么就需要使用 Mockito 库模拟依赖,辅助单元测试。首先,要添加 Mockito 的依赖(版本号应参考最新版本说明):
1
| textCompile 'org.mockito:mockito-core:1.10.19'
|
而且,需要在包裹测试代码的类生命前标记注解 @RunWith(MockitoJUnitRunner.class)
,同时对需要模拟的对象标记注解 @Mock
。然后用方法 when()
包裹被模拟对象可能调用的方法,后接 thenReturn()
方法传入模拟的返回值。一个简单的示例为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| @RunWith(MockitoJUnitRunner.class)
public class UnitTestSample {
private static final String FAKE_STRING = "HELLO WORLD";
@Mock
Context mMockContext;
@Test
public void readStringFromContext_LocalizedString() {
// Given a mocked Context injected into the object under test...
when(mMockContext.getString(R.string.hello_word))
.thenReturn(FAKE_STRING);
ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext);
// ...when the string is returned from the object under test...
String result = myObjectUnderTest.getHelloWorldString();
// ...then the result should be the expected one.
assertThat(result, is(FAKE_STRING));
}
}
|
如果调用了没有使用 when
方法设置的方法,那么测试就会发生错误。
测试套件的声明非常简单,在需要声明为套件的类声明之前加上注解 @RunWith(Suite.class)
,以及 @Suite.SuiteClasses(UnitTest1.class, UnitTest2.class)
,其中 UnitTest1.class
和 UnitTest2.class
代表套件应包含的测试用例的类名。比如:
1
2
3
4
5
6
| @RunWith(Suite.class)
@Suite.SuiteClasses({
TestJunit1.class ,TestJunit2.class
})
public class TestSuite {
}
|
使用测试套件可以更方便的管理测试用例,而且测试套件可以嵌套测试套件类,实现多层树形结构。
@Before
注解用来标记方法使之在所有测试方法之前执行,一般用来初始化必要的测试环境,比如:注入依赖、开启持久化连接、准备复杂的输入参数等。
@After
用来标明方法使之在所有测试方法执行之后运行,一般用来回收资源和清理测试环境,比如:注销依赖、关闭持久化连接、清空输入和输出等。
@Ignore
标记的测试方法不会被执行,可以用来标记一些待完成或者暂时不适合运行的测试方法。
@Test(timeout=xxx)
可设置为正整数数值,单位为毫秒,测试方法如果超过设置的时长就会失败。
@Test(expected=xxxException.class)
设置为异常类类型,测试代码是否抛出了制定种类的异常。
实现步骤如下:
- 用 `@RunWith(Parameterized.class)`` 来注释测试类
- 创建一个由
@Parameterized.Parameters
注释的公共的静态方法,它返回一个对象的集合(数组)来作为测试数据集合。 - 创建一个公共的构造函数,它接受和一行测试数据相等同的东西
- 为每一列测试数据创建一个实例变量
- 用实例变量作为测试数据的来源来创建你的测试用例
比如,对于如下方法进行测试:
1
2
3
4
5
6
7
8
9
10
| 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;
}
}
|
测试类为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| 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));
}
}
|
测试的结果为:
1
2
3
4
5
6
| Parameterized Number is : 2
Parameterized Number is : 6
Parameterized Number is : 19
Parameterized Number is : 22
Parameterized Number is : 23
true
|