第二章 探索核心JUnit
在第一章中,我们决定需要一个可靠和可重复的方式去测试我们程序。我们的解决方案是编写或者重用一个框架去使用我们程序的API来驱动测试代码。随着我们的程序不断增长,新的类和新的方法添加到已存在的类中,我们也需要去增加我们的测试代码。经验告诉我们有时类之间会以意想不到的方式进行交互。我们需要去确认我们能在任何时间运行所有的测试。不管代码是否改变。但是问题来了,我们如何运行多个测试?我们如何发现哪些测试通过哪些测试失败?
在这一章中,我们看JUnit提供的功能如何去解决这些问题。我们从JUnit核心概念开始:测试类、测试套件、测试运行器。在我们重新审视我们的老朋友测试类之前,我们密切关注核心测试运行器和测试套件。我们也研究核心测试类如何在一起工作。
然后,在下面的章节中,我们使用一个样例应用程序去展示如何使用JUnit核心概念。我们演示最优秀的实践去编写和组织测试代码。
2.1探索核心JUnit
在第一章中,CalculatorTest程序定义了拥有一个单独测试方法testAdd的测试类。列表
2.1。
定义一个测试类必须声明public访问控制权限并包含一个无参构造方法。在我们的例子中,因为我们没有定义任何其他的构造方法,所以我们没有必要定义无参构造方法,Java会创建一个默认的构造方法。
创建一个测试方法必须使用@Test注释,同时声明成public,没有参数和返回类型为void。
LIsting 2.1 The calculatortest test case
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class CalculatorTest{
@Test
public void testAdd(){
CalculatorTest calculator = new Calculator();
double result = calculator.add(1,1);
assertEquals(2,result,0);
}
}
在每一个@Test注释方法之前,JUnit创建一个测试类的实例。在测试代码中它帮助提供独立的测试方法和防止意想不到的副作用。因为每一个测试方法运行在一个测试类实例上。我们在测试方法中不能重复使用变量实例值。
执行测试验证。我们使用JUnit中Assert类提供的assert方法。就像在之前的例子中看到的那样,在我们的测试类开始处静态地导入这些assert方法。作为选择,我们能导入JUnit Assert类自身。根据我们的习惯选择静态导入形式。表2.1列出一些流行的assert方法。
Assert方法具有两个参数值遵循一个值得记忆的模式。第一个参数(A in The table)是
期望的值,第二个参数(B in the table)是实际的值。message是执行断言失败的提示信息。
JUnit提供许多其它的方法,例如assertArrayNotEquals,assertNotSame,assertNotTrue,等等。它也使用不同的签名提供相同的方法-没有message参数。这是一个最好的实践对所有你的assert方法调用提供一个错误信息。调用墨菲定律应用在这里,当一个断言失败,通过一个人们可读的信息描述到底出了什么问题。
table 2.1 JUnit assert method sample
assertXXX method |
What it’s used for |
assertArrayEquals(“message”,A,B) |
验证数组A和B内容是否相等 |
assertEquals(“message”,A,B) |
验证对象A和B内容是否相等。这个assert涉及到equals()方法,第一个对象与第二个对象对比。 |
assertSame(“message”,A,B) |
验证对象A和B的引用是否指向同一个对象。而前面的断言方法检查的是A和B有相同的值(使用equals方法),assertSame方法检查A和B两个对象是否是一个对象(引用地址一样)(同 ==操作符功能一样)。
|
assertTrue(“message”,A,B) |
验证A条件是否为真。 |
assertNotNull(“message”,A,B) |
验证A对象是否为空值。 |
当你需要一次运行多个测试类时,你需要创建一个测试套件(Suite),你的测试套件是一个特殊的测试运行器(Runner),因此你能将它作为一个测试类来运行,当你理解何为测试类,套件,运行器时,你将能够编写任何你想要的测试。这三个对象就是JUnit框架的骨架。
在日常的基础上,你仅需要编写测试类和测试套件,而其它的类则是工作在后台,他们一起带你进去测试的世界。
定义(Definitions):
测试类Test class(测试用例TestCase):一个类包含一个或多个使用注释@Test
表现的测试方法。使用一个测试用例将常见行为的测试运行组织在一起。在本书的其余部分,当我们提到一个测试时(a test),意味是我们着使用@Test注释的一个方法。当我们提到一个测试用例时(a test case ),意味着一个类包含一些测试方法--测试集合。通常情况下一个产品类和一个测试类都是一一对应的。
测试套件(test suite):一组测试集合,一个测试套件是将相关的测试组织在一起最便利的方式。例如,假如你没有对一个测试用例定义一个测试套件,JUnit将自动地提供一个测试套件将包括所有的测试,测试套件通常组织测试用例同包的概念一样。
运行器(test runner):测试套件运行器,JUnit提供多种运行器执行测试,我们将在后
见面的章节中覆盖运行器,同时展示如何编写自定义运行器。
Table 2.2 JUnit core objects
Junit 概念 |
职责 |
介绍 |
Assert |
让你定义你想要测试的条件,当一个命题执行成功assert方法是静默的,但是当一个命题失败将抛出一个异常。 |
Section 2.1 |
Test |
使用@Test注释定义一个测试,运行此方法JUnit构建一个类的新实例,然后调用已注释的方法。 |
Section 2.1 |
Test class |
一个测试类包含多个@Test注释方法 |
Section 2.1 |
Suite |
套件允许你将多个测试用例组织在一起 |
Section 2.3 |
Runner |
运行器执行扩展测试。JUnit4向后兼容JUnit3 |
Section 2.2 |
让我们仔细看看组成JUnit核心的每一个对象的职责。看table2.2
我们来详细解释表中的对象以及我们还没有见过的:如test Runner 和test Suite对象。
运行一个基本的测试用例,你不需要做任何特别的操作,JUnit使用一个测试Runner代替你去管理测试用例的生命周期。包括创建类,相关测试,收集结果。接下里章节的情况可能需要你设置你的测试在一个特殊的方式下运行。其中一种情况可以降低一个常见问题的复杂度,如当创建的测试涉及到不同输入的时候。在看Junit提供剩余的测试运行器之前,我们在后面的章节使用一个例子讨论这个特别的场景。
2.2运行参数化测试
这个Parameterized测试运行器允许你使用不同的参数集执行一个测试多次。列表2.2展示Parameterized运行器的一个实例(你能找到这个测试代码在第一章)。
Listing 2.2 Parameterized tests
[...]
@RunWith(value=Parameterized.class) 1
public class ParamaterizedTest{
private double expected;
private double valueOne; 2
private double valueTwo;
@Parameters
public static Collection
return Arrays.asList(new Integer[][]){
{2,1,1}, //expected,valueOne,valueTwo
{3,2,1}, //expected,valueOne,valueTwo
{4,3,1}, //expected,valueOne,valueTwo
});
}
public ParameterizedTest(double expected,double valuesOne,double valueTwo){ 4
this.expected = expected;
this.valueOne = valueOne;
this,valueTwo = valueTwo;
}
@Test
public void sum(){ 5
Calculator calc = new Calculator(); 6
assertEquals(expected,calc.add(valueOne,valueTwo),0); 7
}
}
使用Parameterized测试运行器执行一个测试类,你必须遵从以下的要求。
1.测试类必须进行@RunWith注释,同时将Parameterized类作为@RunWith的参数。
2.你必须在测试中声明变量实例同时使用@Parameters注释参数方法
3.这里调用getTestParameters方法,这个方法的签名必须是@Parameters public static
java.util.Collection.且没有参数。Colletion元素必须是相同长度的数组。这个数组的长度必
须匹配公共构造方法参数的数量。在我们的用例中,每一个数组包括三个元素因为公共构造方法有三个参数。我们的例子使用这个方法去提供测试的输入和期望的输出。因为我们想要测试Calculator程序中的add方法。我们提供三个参数:expected,valueOne和valueTwo,后两个参数是我们用来计算的值。
4.我们指定测试所需的构造函数。请注意,本次测试没有无参构造方法,而是用一个有参构造方法接收测试的参数。
5.6.7.我们最后实现使用@Test注释的sum方法。它是Calculator程序的实例化。验证我们提供的参数。
运行这个测试将精确地循环多次,循环次数同使用@Parameters方法返回的collection长度一样。执行这样单个测试用例的结果同使用多个不同参数测试用例的执行结果是一样的。
sum: assertEquals(2, calculator.add(1, 1), 0);
sum: assertEquals(3, calculator.add(2, 1), 0);
sum: assertEquals(4, calculator.add(3, 1), 0);
这是值得一步一步通过JUnit运行时要了解强大的功能:
JUnit调用静态方法getTestParameters,下一步,JUnit循环每一个数组在getTestParameters 参数集合中。JUnit然后调用唯一的公共构造方法。假如这里有多个公共构造方法,Junit抛出一个断言错误。JUnit然后调用构造方法使用从数组元素中内置的参数列表。在我们的用例中,JUnit调用了三个参数的构造方法使用数组中的第一元素,它自身是一个数组{2,1,1}。JUnit然后调用每一个@Test注释的方法。JUnit重复这个过程使用下一个数组在getTestParameters参数集合中。
当你把这个测试结果同先前的例子比较。这个参数化JUnit测试运行器运行同样的方法三次。每一值来自@Parameters注释的集合。
这个JUnit类Parameterized是众多JUnit测试运行器中的一个。一个测试运行器允许你告诉JUnit一个测试将如何运行。下一节,我们将看到其它的JUnit运行器。
未完待续.....