JUnit是java开发人员的一个主要的测试工具,做Android开发同样是离不开java的,所以Android单元测试依然可以基于JUnit来写测试。但是JUnit只能运行在纯java环境上,前面我们介绍过MVP架构下,可以将View层隔离开来,单独针对Presenter层、Model层来测试。
4.1 JUnit4配置
在build.gradle中加入依赖配置,采用JUnit4框架。
testCompile 'junit:junit:4.12'
JUnit文件夹在工程中的位置,工程创建后,在app -> src目录下,会有3个文件夹:androidTest, main, test。test文件目录就是JUnit单元测试默认的目录。
创建工程时,默认会生成一个单元测试例子ExampleUnitTest.java,且看看一个最简单的Junit单元测试是怎么写的:
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
里面只有一个测试方法,方法上有一个@Test注解,选中方法名,右键选中“Run 'addition_isCorrect()'”执行单元测试,会出现执行结果:
“OK”表示单元测试运行通过,到这里我们已经在Android上执行了一次单元测试。
4.2 JUnit4基础方法注解
JUnit3是通过对测试类和测试方法的命名来确定是否是测试,如测试方法必须以test开头。而在JUnit4中,是通过注解来确定的。
@Test
说明该方法是测试方法。测试方法必须是public void,可以抛出异常。
@Before
它会在每个测试方法执行前都调用一次。
@After
与@Before对应,它会在每个测试方法执行完后都调用一次。
@BeforeClass
它会在所有的测试方法执行之前调用一次。与@Before的差别是:@Before注解的方法在每个方法执行前都会调用一次,有多少个测试方法就会掉用多少次;而@BeforeClass注解的方法只会执行一次,在所有的测试方法执行前调用一次。注意该注解的测试方法必须是public static void修饰的。
@AfterClass
与@BeforeClass对应,它会在所有的测试方法执行完成后调用一次。注意该注解的测试方法必须是public static void修饰的。
@Ignore
忽略该测试方法,有时我们不想运行某个测试方法时,可以加上该注解。
以上这些注解都是针对测试方法而言的,并且是一些常用的注解,我们会频繁用到。我们写个测试类来运行一下,看看具体的执行顺序,代码如下:
public class TestJUnitLifeCycle {
@BeforeClass
public static void init() {
System.out.println("------init()------");
}
@Before
public void setUp() {
System.out.println("------setUp()------");
}
@After
public void tearDown() {
System.out.println("------tearDown()------");
}
@AfterClass
public static void finish() {
System.out.println("------finish()------");
}
@Test
public void test1() {
System.out.println("------test1()------");
}
@Test
public void test2() {
System.out.println("------test2()------");
}
}
执行后打印结果如下:
------init()------
------setUp()------
------test1()------
------tearDown()------
------setUp()------
------test2()------
------tearDown()------
------finish()------
4.3 JUnit4常用断言
JUnit提供了一些辅助函数,他们用来帮助我们确定被测试的方法是否按照预期正常执行,这些辅助函数我们称之为断言(Assertion)。JUnit4所有的断言都在org.junit.Assert类中,Assert类包含了一组静态的测试方法,用于验证期望值expected与实际值actual之间的逻辑关系是否正确,如果不符合我们的预期则表示测试未通过。
assertEquals([message], expected, actual)
验证期望值与实际值是否相等,如果相等则表示测试通过,不相等则表示测试未通过,并抛出异常AssertionError。message表示自定义错误信息,为可选参数,以下均雷同。
assertNotEquals([message], unexpected, actual)
验证期望值与实际值不相等。
assertArrayEquals([message], expecteds, actuals)
验证两个数组是否相同
assertSame([message], expected, actual)
断言两个引用指向同一个对象。
assertNotSame([message], expected, actual)
断言两个引用指向不同的对象。
assertNull([message], object)
断言某个对象为null。
assertNotNull([message], object)
断言对象不为null。
assertTrue([message], condition)
断言条件为真。
assertFalse([message], condition)
断言条件为假。
4.4 Hamcrest与assertThat
Hamcrest是一个表达式类库,它提供了一套匹配符Matcher,且看其官网的说明:
Hamcrest is a library of matchers, which can be combined in to create flexible expressions of intent in tests. They've also been used for other purposes.
前面提到的那些断言方法,大家使用起来会可能会碰到一个问题:
断言通常必须使用一个固定的expected值,如果测试数据稍微有一点变化,测试就可能不通过,这使得测试非常脆弱。例如我们断言assertEquals(0, code),只能判断code是不是为0,如果code不等于0则测试失败,如果code = 0或者 code = 1都是符合我的预期的呢?那又该如何测试。
JUnit4结合Hamcrest提供了一个全新的断言语法:assertThat,结合Hamcrest提供的匹配符,可以表达全部的测试思想,上面提到的问题也迎刃而解。
使用gradle引入JUnit4.12时已经包含了hamcrest-core.jar、hamcrest-library.jar、hamcrest-integration.jar这三个jar包,所以我们无需额外再单独导入hamcrest相关类库。
assertThat定义如下:
public static void assertThat(String reason, T actual,
Matcher super T> matcher)
4.4.1 字符串相关匹配符
startsWith
endsWith
containsString
equalToIgnoringCase
equalToIgnoringWhiteSpace
4.4.2 数值相关匹配符
closeTo
greaterThan
lessThan
lessThanOrEqualTo
greaterThanOrEqualTo
4.4.3 集合相关匹配符
hasEntry
hasKey
hasValue
hasItem
hasItems
hasItemInArray
4.4.4 对象相关匹配符
notNullValue
nullValue
sameInstance
instanceOf
hasProperty
4.4.5 组合等逻辑匹配符
allOf
anyOf
both
either
is
isA
not
any
anything
//文本
assertThat("android studio", startsWith("and"));
assertThat("android studio", endsWith("dio"));
assertThat("android studio", containsString("android"));
assertThat("android studio", equalToIgnoringCase("ANDROID studio"));
assertThat("android studio ", equalToIgnoringWhiteSpace(" android studio "));
//数字
//测试数字在某个范围之类,10.6在[10.5-0.2, 10.5+0.2]范围之内
assertThat(10.6, closeTo(10.5, 0.2));
//测试数字大于某个值
assertThat(10.6, greaterThan(10.5));
//测试数字小于某个值
assertThat(10.6, lessThan(11.0));
//测试数字小于等于某个值
assertThat(10.6, lessThanOrEqualTo(10.6));
//测试数字大于等于某个值
assertThat(10.6, greaterThanOrEqualTo(10.6));
//集合类测试
Map map = new HashMap();
map.put("a", "hello");
map.put("b", "world");
map.put("c", "haha");
//测试map包含某个entry
assertThat(map, hasEntry("a", "hello"));
//测试map是否包含某个key
assertThat(map, hasKey("a"));
//测试map是否包含某个value
assertThat(map, hasValue("hello"));
List list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
//测试list是否包含某个item
assertThat(list, hasItem("a"));
assertThat(list, hasItems("a", "b"));
//测试数组是否包含某个item
String[] array = new String[]{"a", "b", "c", "d"};
assertThat(array, hasItemInArray("a"));
//测试对象
//测试对象不为null
assertThat(new Object(), notNullValue());
Object obj = null;
//测试对象为null
assertThat(obj, nullValue());
String str = null;
assertThat(str, nullValue(String.class));
obj = new Object();
Object obj2 = obj;
//测试2个引用是否指向的通一个对象
assertThat(obj, sameInstance(obj2));
str = "abc";
assertThat(str, instanceOf(String.class));
//测试JavaBean对象是否有某个属性
assertThat(new UserInfo(), hasProperty("name"));
assertThat(new UserInfo(), hasProperty("age"));
//-------组合逻辑测试--------
//两者都满足,a && b
assertThat(10.4, both(greaterThan(10.0)).and(lessThan(10.5)));
//所有的条件都满足,a && b && c...
assertThat(10.4, allOf(greaterThan(10.0), lessThan(10.5)));
//任一条件满足,a || b || c...
assertThat(10.4, anyOf(greaterThan(10.3), lessThan(10.4)));
//两者满足一个即可,a || b
assertThat(10.4, either(greaterThan(10.0)).or(lessThan(10.2)));
assertThat(10.4, is(10.4));
assertThat(10.4, is(equalTo(10.4)));
assertThat(10.4, is(greaterThan(10.3)));
str = new String("abc");
assertThat(str, is(instanceOf(String.class)));
assertThat(str, isA(String.class));
assertThat(10.4, not(10.5));
assertThat(str, not("abcd"));
assertThat(str, any(String.class));
assertThat(str, anything());
4.5 测试方法执行顺序
当我们运行一个测试类里的所有测试方法时,测试方法的执行顺序并不是固定的,JUnit4提供@ FixMethodOrder注解来配置执行顺序,其可选值有:MethodSorters.NAME_ASCENDING、MethodSorters.DEFAULT、MethodSorters.JVM
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestExecOrder {
@Test
public void testD() {
System.out.println("DDDDD");
}
@Test
public void testA() {
System.out.println("AAAAA");
}
@Test
public void testB() {
System.out.println("BBBBB");
}
@Test
public void testC() {
System.out.println("CCCCC");
}
}
@FixMethodOrder(MethodSorters.NAME_ASCENDING)执行结果(按名称升序排列):
AAAAA
BBBBB
CCCCC
DDDDD
@FixMethodOrder(MethodSorters.JVM)执行结果(每次执行可能都不一样):
CCCCC
DDDDD
AAAAA
BBBBB
4.6 小结
本文介绍了JUnit4在android开发中怎样配置,JUnit的基本用法,JUnit常用的断言机制,以及与Hamcrest结合起来的强大的assertThat断言,这些功能基本上能满足我们绝大部分的单元测试编写。
接下来还会再介绍JUnit里更好玩的、更高级的用法。