之前对单元测试做了一个简单的介绍,并说明了编写单元测试的重要性。本文主要对单元测试常用的注释做个小结和笔记并分享。

基本注释

Junit 测试框架是业界使用较为广泛的一个单元测试框架,Android 测试支持库中也集成了该测试框架。JUnit 框架为了方便编写测试用例,提供了一些公共的测试:

  • @Before: 测试用例初始化操作,在测试类内部的每个测试方法运行之前都会运行一次该初始化方法,可以定义多个以@Before注释的方法;
  • @After: 测试用例收尾,包括释放资源和内存等。在测试类内部的每个测试方法运行之后都会运行一次该初始化方法,可以定义多个以@After注释的方法;
  • @Test: 一条测试用例;
  • @BeforeClass: 测试的初始化方法,和@Before的区别在于:@BeforeClass注释的方法在整个测试类中只运行一次,主要用于一些比较耗时或者耗费资源的操作;
  • @AfterClass: 测试的初始化方法,和@After的区别在于:@AfterClass注释的方法在整个测试类中只运行一次,主要用来释放资源;

所以整个测试类的运行流程如下:

test-run-process

假设一个测试类中有名为 testMethod1~testMethod3 的三个测试方法,每个测试方法、@Before、@After、@BeforeClass、@AfterClass注释的方法都打印各自的方法名,如下:

@BeforeClass
public static void beforeClass() { System.out.println("beforeClass method running..."); }

@AfterClass
public static void afterClass() { System.out.println("afterClass method running..."); }

@Before
public void setUp() { System.out.println("setUp method running..."); }

@After
public void tearDown() { System.out.println("tearDown method running..."); }

@Test
public void testMethod1() { System.out.println("testMethod1 testing..."); }

@Test
public void testMethod2() { System.out.println("testMethod1 testing..."); }

@Test
public void testMethod3() { System.out.println("testMethod1 testing..."); }

运行结果如下:

annotation-test-result

虽然在 @BeforeClass中可以初始化一些全局变量,但是不介意这么做,因为如果 new 一个对象,同时这个对象内部持有静态变量,则可能测试用例之间因为这个静态变量的改变相互影响而导致测试不准确。

除了上述注释以外,JUnit 框架还提供了其它一些注释:

异常测试 expteted

异常测试是指验证当前被测试代码或者测试代码会抛出一个特定的异常,例如:

// 期待此用例抛出 AssertionError 异常, 只有抛出该异常,用例才算成功
@Test(expected = AssertionError.class)
public void exceptionTest() {
assertEquals(1, 2)
}

上述用例只有一个断言语句,在测试的过程中这个断言语句必定会抛出 AssertionError异常,我们在@Test注释的时候添加我们对该条测试用例所期待的异常,即AssertionError.class

超时测试 timeout

超时测试 是指 可以对一个测试用例运行时间进行测试,超过规定时间则认为该测试用例失败。可以用来测试网络数据读取时间或者数据库读取存储等,例如:

@Test (timeout = 800)
public void timeoutTest() {
while (true);
}

上述用例中写了一个 while 死循环,如果不指定超时时间,该用例将会无限运行下去,知道手动终止。同时指定该用例时间必须是 <= 800ms,当用例运行时间超过 800ms 时,判定该测试用例为失败。

分类测试@Category

分类测试 是指可以对测试用例进行分类,对测试用例或者测试类添加 @Category({CategoryA.class, CategoryB.class}, ...)对其进行分类 ,例如:

// 定义两个Category
public interface Category1 { String TAG = "Category1"; /* marker */ }
public interface Category2 { String TAG = "Category2"; /* marker */ }
// 定义两个测试用例以及分类
@Category({Category1.class, Category2.class})
// 所有此测试类内部的测试用例都属于两个类别
public class TestA {
@Test
public void methodA() {
// do some test here
}
}
public class TestB {
@Test
@Category(Category1.class) // 对方法单独知道分类
public void methodB1() { }

@Test
@Category(Category2.class)
public void methodB2() { }
}
// 建立分类测试集
@RunWith(Categories.class)
@Suite.SuiteClasses({TestA.class, TestB.class})
@Categories.IncludeCategory(Category1.class)
@Categories.ExcludeCategory(Category2.class)
public class Category1TestSuit {
// 运行 TestB.methodB1
}

@RunWith(Categories.class)
@Suite.SuiteClasses({TestA.class,TestB.class})
@Categories.IncludeCategory({Category2.class})
public class Category2TestSuit {
// 只运行 TestA.methodA 和 TestB.methodB2
}

参数测试@Parameters

参数测试需要在测试类内部先声明需要的参数,并且需要构造函数进行复制,同时需要一个@Parameters诸事的方法进知道参数的取值,做好这些,剩余的事情就由 JUnit 来完成了。以下例子为计算一个简单的计算简称的测试,有一个输入和一个输出:

@RunWith(Parameterized.class)
public class ParameterTest {
private int inputNum;
private int expectedResult;

public ParameterTest(int inputNum, int expectedResult) {
this.inputNum = inputNum;
this.expectedResult = expectedResult;
}

@Parameterized.Parameters
public static Collection getParaList() {
return Arrays.asList(new Object[][]{
{0, 1},
{1, 1},
{2, 2},
{5, 120}
});
}

@Test
public void factorialTest() {
assertEquals(expectedResult, calculate(inputNum));
}

private int calculate(int num) {
if (num == 0 || num == 1) {
return 1;
}
return num * calculate(num - 1);
}
}

忽略测试@Ignore

当在调试过程中可能在某些情况下需要跳过一条测试用例、或者一个测试类不让其执行。当某一条测试用例、一个测试类以@Ignore注释时,此用例、或者此测试类内部所有的测试用例不会被执行。

@Test
@Ignore
public void ignoreTest() {
// do some test here, but it not run.
}

套件测试

套件测试个分类测试有点类似。分类测试时把类似功能的用例分类到一起,而套件测试则是把一个或多个测试类集中在一起运行。例如:

// test calss A
public class TestA {
@Test
public void methodA() {
// do some test here
}
}
// test class B
public class TestB {
@Test
public void methodB1() { }

@Test
public void methodB2() { }
}
// 定义测试集
@RunWith(Suite.class)
@Suite.SuiteClasses({TestA.class, TestB.class}) // 包含 TestA 和 TestB 两个测试类
public class SuitTestClass {
// 运行 TestA.methodA, TestB.methodB1 以及 TestB.methodB2
}

运行结果如下:

suite-test-result

小结

JUnit 框架定义了许多注释,给测试带来了很大方便,可以利用这些注释来编写更全面、更优雅以及更能达到测试目的的测试用例。