(一)JUnit介绍
目录
1.什么是单元测试?
单元测试负责对最小的软件设计单元(模块)进行验证,根据软件设计文档中对模块功能的描述,对重要的程序分支进行测试并发现错误。
2.什么是单元测试框架?
对于单元测试框架来讲,它主要完成以下几件事。
提供用例组织与执行:测试用例只有几条时,可以不考虑用例组织,但是用例达到成百上千时,大量的测试用例堆砌在一起,就产生了扩展性与维护性等问题
提供丰富的断言方法:不论是功能测试,还是单元测试,在用例执行完之后都需要将实际结果与预期结果相比较(断言),从而断定用例是否执行通过。单元测试框架一般提供丰富的断言方法。例如:判断相等/不等、包含/不包含、True/False的断言方法等
提供丰富的日志: 当测试用例执行失败时能抛出清晰的失败原因,当所有用例执行完成后能提供丰富的执行结果。例如,总执行时间、失败用例数、成功用例数等。
从这些特性来看单元测试框架的作用是:帮助我们更自动化完成测试,所以,它是自动化测试的基础。
3.什么是JUnit?
Junit 官网:http://junit.org/
JUnit 是一个编写可重复测试的简单框架。它是单元测试框架的 xUnit 架构的一个实例。
(二)JUnit 安装
Junit目前分两个版本,Junit4 和 Junit5 , 本系列教程打算从 Junit4 开始介绍,最后,再介绍 Junit5 有哪些新特性
1.IntelliJ IDEA 安装 Junit
Java 开发的同学,推荐使用 IntelliJ IDEA,推荐阅读《IntelliJ IDEA 教程》。
1、下载 junit-4.12.jar 文件:https://github.com/junit-team/junit4/releases
2、 打开 IntelliJ IDEA ,菜单栏:File菜单 –> Porject Structure 选项 –> Dependencies 标签 –> 点击 “+” 号 –> Library… –> Java 。 选择下载的 junit-4.12.jar 进行添加。
3、以同样的方式下载和导入 hamcrest: https://github.com/hamcrest/JavaHamcrest/releases ,否则,你将无法运行 Junit 单元测试。
2.Maven 安装 Junit
相比较而言,Maven 的安装要简单很多,打开你 Maven 项目中的 pom.xml 文件,添加如下配置:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
更多的 Maven 项目,可以登录:https://www.mvnrepository.com 网站查找。
(三)JUnit 编写单元测试
1.编写单元测试
创建 JunitDemo 类,编写第一个单元测试用例。
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class JunitDemo {
@Test
public void myFirstTest() {
assertEquals(2+2, 4);
}
}
@Test 用来注释一个普通的方法为一条测试用例。
assertEquals() 方法用于断言两个值是否相关。
2.测试功能模块
创建一个被测试类:Count ,代码如下:
public class Count {
/**
* 计算并返回两个参数的和
*/
public int add(int x ,int y){
return x + y;
}
}
Count 类的实现非常简单,看注释就可以了。
接下来,创建 CountTest 类,用于测试 Count 类。
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class CountTest {
@Test
public void testAdd() {
Count count = new Count();
int result = count.add(2,2);
assertEquals(result, 4);
}
}
new 出 Count 类,调用 add() 方法并传参,通过 assertEquals() 断言 返回结果。
恭喜! 你已经会编写单元测试了。
(四)JUnit 注解
1.JUnit 注解
JUnit 注解说明:
注解 | 说明 |
---|---|
@Test: | 标识一条测试用例。 (A) (expected=XXEception.class) (B) (timeout=xxx) |
@Ignore: | 忽略的测试用例。 |
@Before: | 每一个测试方法之前运行。 |
@After : | 每一个测试方法之后运行。 |
@BefreClass | 所有测试开始之前运行。 |
@AfterClass | 所有测试结果之后运行。 |
2.例子
创建被测试类 Count .
public class Count {
/**
* 计算并返回两个参数的和
*/
public int add(int x ,int y){
return x + y;
}
/**
* 计算并返回两个数相除的结果
*/
public int division(int a, int b){
return a / b;
}
}
创建测试类 CountTest .
import static org.junit.Assert.assertEquals;
import org.junit.Ignore;
import org.junit.Test;
public class CountTest {
//验证超时
@Test(timeout=100)
public void testAdd() throws InterruptedException {
Thread.sleep(101);
new Count().add(1, 1);
}
//验证抛出异常
@Test(expected=ArithmeticException.class)
public void testDivision() {
new Count().division(8, 0);
}
// 跳过该条用例
@Ignore
@Test
public void testAdd2() {
Count count = new Count();
int result = count.add(2,2);
assertEquals(result, 5);
}
}
1、在 testAdd() 用例中设置 timeout=100 , 说明的用例的运行时间不能超过 100 毫秒, 但故意在用例添加 sleep() 方法休眠 101 毫秒,所以会导致用例失败。
2、在 Java 中被除数不能为0,所以 8⁄0 会报 ArithmeticException 异常, 在 @Test 中设置 expected=ArithmeticException.class ,说明抛该异常符合预期。
3、@Ignore 表来标识该用例跳过,不管用例运行成功还是失败。
执行结果如下:
(五)JUnit 注解之Fixture
继续介绍 JUnit 的注解
什么是Fixture
Test Fixture 是指一个测试运行所需的固定环境,准确的定义:
The test fixture is everything we need to have in place to exercise the SUT
在进行测试时,我们通常需要把环境设置成已知状态(如创建对象、获取资源等)来创建测试,每次测试开始时都处于一个固定的初始状态;测试结果后需要将测试状态还原,所以,测试执行所需要的固定环境称为 Test Fixture。
JUnit 中的 Fixture
被测试类同样使用上一小节的 Count , 创建 TestFixture 测试类。
import static org.junit.Assert.*;
import org.junit.*;
public class TestFixture {
//在当前测试类开始时运行。
@BeforeClass
public static void beforeClass(){
System.out.println("-------------------beforeClass");
}
//在当前测试类结束时运行。
@AfterClass
public static void afterClass(){
System.out.println("-------------------afterClass");
}
//每个测试方法运行之前运行
@Before
public void before(){
System.out.println("=====before");
}
//每个测试方法运行之后运行
@After
public void after(){
System.out.println("=====after");
}
@Test
public void testAdd1() {
int result=new Count().add(5,3);
assertEquals(8,result);
System.out.println("test Run testadd1");
}
@Test
public void testAdd2() {
int result=new Count().add(15,13);
assertEquals(28,result);
System.out.println("test Run testadd2");
}
}
代码中的注释已经对 @BeforeClass、 @AfterClass 、 @Before 、 @After 做了说明。
至于什么时候会用到这些方法跟你具体的业务用例有关,如果是 Web UI 自动化测试,可以把 浏览器驱动的定义放到 @Before中,浏览器的关闭放到 @After 中。
运行结果如下:
(六)JUnit 用例执行顺序
在运行测试的过程中,有时候需要控制用例的执行顺序。
@FixMethodOrder
JUnit 通过 @FixMethodOrder 注解来控制测试方法的执行顺序的。@FixMethodOrder 注解的参数是 org.junit.runners.MethodSorters 对象,在枚举类 org.junit.runners.MethodSorters 中定义了如下三种顺序类型:
- MethodSorters.JVM
Leaves the test methods in the order returned by the JVM. Note that the order from the JVM may vary from run to run (按照JVM得到的方法顺序,也就是代码中定义的方法顺序)
- MethodSorters.DEFAULT(默认的顺序)
Sorts the test methods in a deterministic, but not predictable, order() (以确定但不可预期的顺序执行)
- MethodSorters.NAME_ASCENDING
Sorts the test methods by the method name, in lexicographic order, with Method.toString() used as a tiebreaker (按方法名字母顺序执行)
例子
具体如何使用,看例子,创建 TestRunSequence 测试类。
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import static org.junit.Assert.assertEquals;
// 按字母顺序执行
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestRunSequence {
@Test
public void TestCase1() {
assertEquals(2+2, 4);
}
@Test
public void TestCase2() {
assertEquals(2+2, 4);
}
@Test
public void TestAa() {
assertEquals("hello", "hi");
}
}
MethodSorters.NAME_ASCENDING 设置按字母的顺序执行,所以,TestAa() 先被执行,虽然它在代码中是最后一条用例。
运行结果如下:
(七)JUnit 断言方法
JUnit 断言方法
JUnit 所提供的断言方法:
方法 | 说明 |
---|---|
assertArrayEquals(expecteds, actuals) | 查看两个数组是否相等。 |
assertEquals(expected, actual) | 查看两个对象是否相等。类似于字符串比较使用的equals()方法。 |
assertNotEquals(first, second) | 查看两个对象是否不相等。 |
assertNull(object) | 查看对象是否为空。 |
assertNotNull(object) | 查看对象是否不为空。 |
assertSame(expected, actual) | 查看两个对象的引用是否相等。类似于使用“==”比较两个对象。 |
assertNotSame(unexpected, actual) | 查看两个对象的引用是否不相等。类似于使用“!=”比较两个对象。 |
assertTrue(condition) | 查看运行结果是否为true。 |
assertFalse(condition) | 查看运行结果是否为false。 |
assertThat(actual, matcher) | 查看实际值是否满足指定的条件。 |
fail() | 让测试失败。 |
例子
关于断言方法,我们前面用得最多的是 assertEquals ,用于断言两个对象是否相等。这里再介绍一个 assertTrue 的使用。
创建 AssertTest 测试类(包了含被测试方法):
import org.junit.*;
import static org.junit.Assert.*;
public class AssertTest {
/**
* 判断一个数是否为素数
*/
public static Boolean Prime(int n) {
for (int i = 2; i < Math.sqrt(n); i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
@Test
public void testPrime(){
int n = 7;
assertTrue(AssertTest.Prime(n));
}
}
Prime() 方法用于判断一个数是否为素数(只能被1和它本身整除的数),并返回 True 或 False ,在测试用例中通过 assertTrue来断言结果。
(八)JUnit 测试批量运行
前面测试用例的运行 主要针对单个测试类进行的,当然,在 IntelliJ IDEA 中也可以选择单个的方法执行。那如果我们想运行所有的用例的文件呢?
IntelliJ IDEA 中设置运行
设置
在 IntelliJ IDEA 中,菜单栏:Run菜单 –> Edit Configurations…选项。
在 Junit 目录下,选择任意一个用例文件。
- Test Kind : 选择用例的运行类型/级别。
- packages : 选择用例运行的目录,即你的测试用例目录。
设置完成后,点击 “OK” 按钮。
运行
点击 IntelliJ IDEA 工具栏上的运行按钮,来运行 test 目录下的所有用例。
运行结果:
通过测试套件运行
这种方法引入一种 “测试套件” 的概念,JUnit 提供了一种批量运行测试类的方法,叫测试套件。
测试套件的写法需要遵循以下原则:
-
创建一个空类作为测试套件的入口;
-
使用注解 org.junit.runner.RunWith 和 org.junit.runners.Suite.SuitClasses 修饰这个空类。
-
将 org.junit.runners.Suite 作为参数传入给注解 RunWith,以提示 JUnit 为此类测试使用套件运行器执行。
-
将需要放入此测试套件的测试类组成数组作为注解 SuiteClasses 的参数。
-
保证这个空类使用public修饰,而且存在公开的不带任何参数的构造函数。
单独创建一个测试类 runAllTest .
package test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
CountTest.class,
TestFixture.class,
AssertTest.class,
TestRunSequence.class,
})
public class runAllTest {
}
把需要运行的测试类放到 SuiteClasses 中,运行 runAllTest 测试类,即可批量执行测试用例。
(九)JUnit5 介绍与安装
Junit5 已经不算是新的版本了,2016 年推出非正式版,相比较 JUnit4 安装和使用都有一定的差异。
JUnit5 介绍
The new major version of the programmer-friendly testing framework for Java 8
一个新的重要版本,程序员更友好的测试框架,基于 Java8。
关于
JUnit5 是 JUnit 的下一代。我们的目标是为 JVM 上的开发人员端测试创建一个最新的基础。这包括针对 Java 8 及以上,以及使许多不同风格的测试。
Junit5 组成
先看来个公式:
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
这看上去比 Junit4 复杂,实际上在导入包时也会复杂一些。
-
JUnit Platform 是在JVM上启动测试框架的基础。
-
JUnit Jupiter 是JUnit5扩展的新的编程模型和扩展模型,用来编写测试用例。Jupiter子项目为在平台上运行Jupiter的测试提供了一个TestEngine (测试引擎)。
-
JUnit Vintage 提供了一个在平台上运行 JUnit3 和 JUnit4 的 TestEngine 。
Maven 安装
首先,你需要通过 IntelliJ IDEA 创建一个 Maven 项目,IntelliJ IDEA 集成的有 Maven,所以,你很容易做到这一点。通过 Maven 的 pom.xml 文件,添加 Junit5 。
pom.xml 文件配置如下:
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>4.12.1</version>
<scope>test</scope>
</dependency>
</dependencies>
(十)JUnit5 创建测试
创建测试用例
我在 IntelliJ IDEA 中创建的 Maven 项目,目录结构如下:
在 test.java 目录下创建一个 FistJUnit5Tests 类。代码如下:
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class FirstJUnit5Tests {
@Test
void myFirstTest() {
assertEquals(2, 1 + 1);
}
}
明显看出和 Junit4 还是有些不同的。
首先,导入测试测试注解(@Test)和断言方法(assertEquals)的路径不同。
其次,不需要手动把测试和测试方法声明为 public 了。
(十一)JUnit5 新的用法
创建 JUnit5NewTests 测试类。
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertAll;
import org.junit.jupiter.api.*;
class JUnit5NewTests {
@BeforeEach
@DisplayName("每条用例开始时执行")
void start(){
}
@AfterEach
@DisplayName("每条用例结束时执行")
void end(){
}
@Test
void myFirstTest() {
assertEquals(2, 1 + 1);
}
@Test
@DisplayName("描述测试用例╯°□°)╯")
void testWithDisplayName() {
}
@Test
@Disabled("这条用例暂时跑不过,忽略!")
void myFailTest(){
assertEquals(1,2);
}
@Test
@DisplayName("运行一组断言")
public void assertAllCase() {
assertAll("groupAssert",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}
@Test
@DisplayName("依赖注入1")
public void testInfo(final TestInfo testInfo) {
System.out.println(testInfo.getDisplayName());
}
@Test
@DisplayName("依赖注入2")
public void testReporter(final TestReporter testReporter) {
testReporter.publishEntry("name", "Alex");
}
}
用法都已经通过测试用例的 @DisplayName 进行了说明,这里不再解释。
运行结果如下:
(十二)补充:JUnit 注解之Rule
一个JUnit Rule就是一个实现了TestRule的类,这些类的作用类似于 @Before
、@After
,是用来在每个测试方法的执行前后执行一些代码的一个方法。 那为什么不直接用这些 @Before
、@After
呢?这是因为它们都只能作用于一个类,如果同一个setup需要在两个类里面同时使用,那么你就要在两个测试类里面定义相同的@Before方法,然后里面写相同的代码,这就造成了代码重复。
此外,JUnit Rule还能做一些 @Before
、@After
这些注解做不到的事情,那就是他们可以动态的获取将要运行的测试类、测试方法的信息。
使用框架自带的Rule
除了增加Rule特性,新版JUnit还添加了很多核心Rule
- TemporaryFolder:测试可以创建文件与目录并且会在测试运行结束后将其删除。这对于那些与文件系统打交道且独立运行的测试来说很有用。
- ExternalResource:这是一种资源使用模式,它会提前建立好资源并且会在测试结束后将其销毁。这对于那些使用socket、嵌入式服务器等资源的测试来说很有用。
- ErrorCollector:可以让测试在失败后继续运行并在测试结束时报告所有错误。这对于那些需要验证大量独立条件的测试来说很有用(尽管这本身可能是个“test smell”)。
- ExpectedException:可以在测试中指定期望的异常类型与消息。
- Timeout:为类中的所有测试应用相同的超时时间。
例如,TimeOut这个Rule的使用。
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
public class RuleTestDemo {
//使用Timeout这个Rule
@Rule
public Timeout timeout = new Timeout(1000);
@Test
public void testMethod1() throws Exception {
Thread.sleep(1001);
}
@Test
public void testMethod2() throws Exception {
Thread.sleep(999);
}
}
使用JUnit所提供的Timeout类,该类用于控制测试用例的执行超时时间。这里设置为1秒,当用例执行超过1秒则失败。接下来分别在 testMethod1和testMethod2两个用例中使用sleep()方法来控制用例的执行时间,显然testMethod1超过1秒,则运行失败。
自定义的Rule
除了可以使用JUnit框架自带的Rule,还可以根据自己的需求自定义Rule。简单来说,自定义一个Rule就是implement一个TestRule 接口,并实现apply()方法。该方法需要返回一个Statement对象。例子如下:
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class MethodNameRule implements TestRule {
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
//在测试方法运行之前做一些事情,在base.evaluate()之前
String className = description.getClassName();
String methodName = description.getMethodName();
base.evaluate(); //运行测试方法
//在测试方法运行之后做一些事情,在base.evaluate()之后
System.out.println("Class name:"+className+", method name: "+methodName);
}
};
}
}
这里实现的功能是在每次测试用例运行之后,打印当前测试用例的类名和方法名。 在上面的例子中添加这里定义的MethodNameRule 。
……
public class RuleTestDemo {
//使用Timeout这个Rule
@Rule
public Timeout timeout = new Timeout(1000);
//使用自定义Rule,
@Rule
public MethodNameRule methodNameRule = new MethodNameRule();
……
再次运行测试用例,执行结果如下: