本文中所讨论的JUnit测试框架基于JUnit4.x。并且将通过Android Studio来讲解JUnit的使用。
在很多地方无法完全用中文来描述,所以有些地方还需英文来描述。
本篇注重概念内容,为后面的测试案例做铺垫
定义测试方法
JUnit使用注解标注为测试方法并配置它们,下表中给出了JUnit4非常重要的注解。下面所有的注解都用在方法上。
JUnit 4 | 描述 |
---|---|
import org.junit.* | 用于导入下列注解。 |
@Test | 将方法标记为测试方法。 |
@Before | 在每次测试之前执行,一般用于准备测试环境(初始化类等)。 |
@After | 在每次测试之后执行,用于清理测试环境 (例如删除临时数据,还原默认值等)。 它也可以拥有清理内存( It can also save memory by cleaning up expensive memory structures.)。 |
@BeforeClass | 在所有测试之前,执行一次。它一般用于执行time intensive activities,例如连接数据库等。使用该注解标记的方法需要定义为static void。 |
@AfterClass | 在所有的测试执行完成之后执行一次。 它一般用于清理一些 activities, 例如断开数据连接。使用该注解标记的方法需要定义为static void |
@Ignore or @Ignore("Why disabled") | 标记该注解的测试方法是被禁用的。这对于实际代码做了修改而测试代码没有修改的情况是非常有用的,或者由于这条测试执行时间过长先不将其包含在测试中,最好是提供一下不去测试的原因。 |
@Test (expected = Exception.class) | 如果这个测试方法不抛出赋值的异常(Exception.class)将会失败。 |
@Test(timeout=100) | 如果这个测试方法执行超过100毫秒将会失败。 |
断言语句(Assert statements)
JUnit提供静态方法,通过Assert类来测试某些条件。这些断言语句通常以assert开头。他们允许你指定错误信息、预期结果和实际结果。断言方法将测试返回的实际值和预期值相比较。如果比较失败就会抛出AssertionException异常。
下表简单的介绍了这些方法。[]中的参数是可选的字符串类型。
语句 | 描述 |
---|---|
fail([message]) | 让这个测试方法失败,可能用于检查代码中某个部分是否未执行,或者在执行测试代码之前是否存在失败的测试。这个message参数是可选的。 |
assertTrue([message,] boolean condition) | 验证boolean条件为true。 |
assertFalse([message,] boolean condition) | 验证boolean条件为false。 |
assertEquals([message,] expected, actual) | 验证expected和actual相同。注:对于数组则检查的是引用而不是数组的内容。 |
assertEquals([message,] expected, actual, tolerance) | 验证float或者double匹配。 大家知道计算机表示浮点型数据都有一定的偏差,所以哪怕理论上他们是相等的,但是用计算机表示出来则可能不是,所以这里运行传入一个偏差值。如果两个数的差异在这个偏差值之内,则测试通过,否者测试失败。 |
assertNull([message,] object) | 验证object为null。 |
assertNotNull([message,] object) | 验证object不为null。 |
assertSame([message,] expected, actual) | 验证expected和actual是同一个对象。 |
assertNotSame([message,] expected, actual) | 验证expected和actual不是同一个对象。 |
JUnit 组合测试(JUnit test suites)
如果你有几个测试类,那么可以使用组合测试。这样在运行时组合测试将会按照顺序执行所有这个组合测试中的测试类。另外组合测试还可以包含其他的组合测试。(呃。。。有些绕)。简单的说就是它可以一次性执行多个测试用例。
下面的例子演示了组合测试的用法。它包含了两个测试类MyClassTest和CalculatorTest。如果继续想添加测试类继续放在@SuiteClasses注解中即可。
package com.lulu.androidtestdemo;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
/**
* Created by zhanglulu on 2018/1/24.
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
MyClassTest.class,CalculatorTest.class
})
public class AllTest {
}
禁用测试
在上文中提到可以使用@ignore注解来标记禁用当前测试方法。或者也可以使用Assume.assumeFalse或Assume.assumeTrue来判断测试条件。Assume.assumeFalse方法的测试条件返回true则表示当前测试方法无效,同理Assume.assumeTrue的测试条件返回false表示当前测试方法无效。
下图中示例就是在Windows系统中禁用addition_isCorrect()方法。其中System.getProperty(“os.name”).contains(“Windows”)称之为测试条件。
package com.lulu.androidtestdemo;
import org.junit.Assume;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
*禁用测试
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
Assume.assumeFalse(System.getProperty("os.name").contains("Windows"));
assertEquals(4, 2 + 2);
}
}
参数化测试(Parameterized test)
JUnit可以在测试类使用参数进行测试。这个类包含一个待测试的方法,并且这个方法可以用不同的参数去执行。
要想将测试类标记为参数化测试,必须使用注解:@RunWith(Parameterized.class)
这样的测试类必须包含一个静态方法,这个方法要使用@Parameters注解标注。而且这个方法要生成并返回一个数组的集合(对,没错,是数组的集合Collection
/**
* Created by zhanglulu on 2018/1/24.
* 参数化测试示例
*/
@RunWith(Parameterized.class)
public class ParameterizedTestFields {
@Parameterized.Parameter(0)
public int m1;
@Parameterized.Parameter(1)
public int m2;
@Parameterized.Parameter(2)
public int result;
// creates the test data
@Parameterized.Parameters
public static Collection<Object[]> data() {
Object[][] data = new Object[][] { { 1 , 2, 2 }, { 5, 3, 15 }, { 121, 4, 484 } };
return Arrays.asList(data);
}
@Test
public void testMultiplyException() {
MyClass tester = new MyClass();
assertEquals("Result", result, tester.multiply(m1, m2));
}
}
Note: 必须提供@Parameter方法,方法的返回必须是public static Collection,不能有参数,并且collection元素必须是相同长度的数组。同时数组的长度必须与测试类的字段(m1,m2,result)的数量相匹配。
如果不使用 @Parameter()标注公共字段,也可以使用构造方法给字段赋值,如代码所示:
/**
* Created by zhanglulu on 2018/1/24.
* 参数化测试示例
*/
@RunWith(Parameterized.class)
public class ParameterizedTestFields {
public int m1;
public int m2;
public int result;
public ParameterizedTestFields(int m1, int m2, int result) {
this.m1 = m1;
this.m2 = m2;
this.result = result;
}
// creates the test data
@Parameterized.Parameters
public static Collection<Object[]> data() {
Object[][] data = new Object[][] { { 1 , 2, 2 }, { 5, 3, 15 }, { 121, 4, 484 } };
return Arrays.asList(data);
}
@Test
public void testMultiplyException() {
MyClass tester = new MyClass();
assertEquals("Result", result, tester.multiply(m1, m2));
}
}
Note : 需要注意的是两者必须有其一,否则会抛出反射参数不匹配的异常:java.lang.IllegalArgumentException: wrong number of arguments
还有其他的实现方案,这里不再详述请移步:https://github.com/Pragmatists/JUnitParams.
分类测试(Category)
Category继承自Suit,可以让我们的测试类中的测试方法进行分类测试,下面的例子就介绍了如何使用Category快速的进行分类测试。
public class A {
@Test
public void a() {
fail();
}
@Category(SlowTests.class)
@Test
public void b() {
}
}
@Category({ SlowTests.class, FastTests.class })
public class B {
@Test
public void c() {
}
}
public interface FastTests {
}
public interface SlowTests {
}
进行分类测试:
@RunWith(Categories.class)
@Categories.IncludeCategory(SlowTests.class)
@Suite.SuiteClasses({ A.class, B.class })
public class SlowTestSuite {
}
JUnit 规则 (JUnit Rule)
简介
Rule是JUnit4的新特性,它可以让我们扩展JUnit的功能,灵活地改变测试方法的行为。JUnit中使用@Rule和@ClassRule两个注解来实现Rule的扩展,这两个注解需要放在实现了TestRule接口的成员变量(@Rule)或者静态变量(@ClassRule)上。@Rule和@ClassRule的不同点是,@Rule是方法级别的,每个测试方法执行时都会调用被注解的Rule,而@ClassRule是类级别的,在执行一个测试类的时候只会调用一次被注解的Rule。
JUnit内置的Rule
ExpectedException
例如下面的示例,用来检测抛出的异常信息是否符合预期。当methodToBeTest方法中抛出IllegalArgumentException异常,并且异常信息中含有“Negative value not allowed”信息时,测试通过。
package com.lulu.androidtestdemo.junit.rule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
* Created by zhanglulu on 2018/1/24.
* JUnit Rules
*/
public class RuleExceptionTesterExample {
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void throwsIllegalArgumentExceptionIfIconIsNull() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("Negative value not allowed");
ClassToBeTested t = new ClassToBeTested();
t.methodToBeTest(-1);
}
}
class ClassToBeTested {
public void methodToBeTest(int i) {
if (i == -1) {
throw new IllegalArgumentException("Negative value not allowed");
}
}
}
TemporaryFolder
TemporaryFolder 类可以在测试之前创建临时的文件或文件夹,并且在测试结束后自动删除。
package com.lulu.androidtestdemo.junit.rule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import static org.junit.Assert.assertTrue;
/**
* Created by zhanglulu on 2018/1/24.
* testUsingTempFolder
*/
public class RuleTester {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void testUsingTempFolder() throws IOException {
File createdFolder = folder.newFolder("newfolder");
File createdFile = folder.newFile("myfilefile.txt");
assertTrue(createdFile.exists());
assertTrue(createdFolder.exists());
}
}
更多的已经实现好的Rule请移步:https://github.com/junit-team/junit4/wiki/Rules