JUint是Java编程语言的单元测试框架,用于编写和运行可重复的自动化测试。本文主要针对Junit4要点进行梳理总结。
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
这其实是问单元测试能带来什么好处,之所以把这个问题放在这里讨论,是因为需要清楚单元测试的使用场景,以及它做得到和做不到的。我们在写好一个函数或者类以后,需要检验我们的程序是否存在bug或者是否满足我们的需求,通常的做法就是将写好的函数在mian方法中调用,输入一些测试用例进行检验。当要检验的方法数量较少时,这种方法可行,但是当我们有大量的函数需要验证时,该方法就显得笨重且繁琐,往往需要我们人工检查输出的结果是否正确等,比较混乱。因此,单元测试就是用来解决这种繁琐问题的。使用单元测试可以有效地降低程序出错的机率,提供准确的文档,并帮助我们改进设计方案等等。以下列举了一些我为什么使用单元测试的好处:
写单元测试的时机不外乎三种情况:
个人比较推荐单元测试与具体实现代码同步进行这个方案的,只有对需求有一定的理解后才能知道什么是代码的正确性,才能写出有效的单元测试来验证正确性,而能写出一些功能代码则说明对需求有一定理解了。
JUint是Java编程语言的单元测试框架,用于编写和运行可重复的自动化测试。其具有如下特点:
最好的资料依然在Junit官方网站,以下我帮你总结下Junit相关的官方网址。
网址 | 说明 |
---|---|
https://junit.org/junit4/ | 官网地址 |
https://junit.org/junit4/docs/current/user-guide/ | 官方入门文档 |
https://github.com/junit-team | 官方github |
Junit4 注解提供了书写单元测试的基本功能,这里列出一些常用的注解,如下表所示:
注解 | 说明 |
---|---|
@Test | 测试注解,标记一个方法可以作为一个测试用例 |
@Ignore | 暂不执行该方法 |
@BeforeClass | 在所有测试之前,只执行一次,且必须为static void |
@AfterClass | 在所有测试之后,只执行一次,且必须为 static void |
@Before | 该方法必须在类中的每个测试之前执,以便执行某些必要的先决条件 |
@After | 该方法在每项测试后执行(如执行每一个测试后重置某些变量,删除临时变量等) |
@Runwith | 测试类的初始化。在测试类的开头标注,表明运行环境 |
@Parameters | 指定测试类的测试数据集合 |
@FixMethodOrder | 指定测试方法的执行顺序 |
现在主流的IDE比如IDEA或者Eclipse都提供了对JUnit4的支持,可以非常方便的使用JUnit4。当你在代码中添加了@Test注解,然后使用IDE的自动补全功能时,一般情况下IDE会弹出对话框询问你是否将JUnit4库添加到项目的类路径下。当然也可以自己手动添加JUnit4的依赖。如果使用Maven,添加如下一段:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>MavenDemoartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
project>
一个 JUnit 测试是一个在专用于测试的类中的一个方法, 并且这个方法被 @org.junit.Test 注解标注。我们只需要在我们要测试的方法上加上 @Test 注解,那么这个方法就会被当做一个单元测试,单独去运行,我们来试一下。
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class HelloWorldTest {
@Test
public void firstTest() {
assertEquals(2, 1 + 1);
}
}
@Test注解在方法上标记方法为测试方法,以便构建工具和 IDE 能够识别并执行它们。在 Junit 3中,是通过对测试类和测试方法的命名来确定是否是测试,且所有的测试类必须继承junit的测试基类。在 Junit 4中,定义一个测试方法变得简单很多,只需要在方法前加上@Test就行了。注意:测试方法必须是public void,即公共、无返回数据。
JUnit4测试用例的完整的生命周期要经历几个阶段,他们分别是类级初始化资源处理、方法级初始化资源处理、执行测试用例中的方法、方法级销毁资源处理、类级销毁资源处理等。这几个阶段分别有对应的注解所标注,如下表
注解 | 说明 |
---|---|
@BeforeClass | 该方法只执行一次,并且在所有方法之前执行,例如创建数据库连接、读取文件等。 |
@AfterClass | 该方法只执行一次,并且在所有方法之后执行。通常用来对资源进行释放,比如数据库连接的关闭等。 |
@Before | 该方法在每一个测试方法之前运行,可以使用该方法进行初始化之类的操作 |
@After | 该方法在每一个测试方法之后运行,可以使用该方法进行释放资源,回收内存之类的操 |
简单来说,使用@BeforeClass 和 @AfterClass 两个注解标注的方法会在所有测试方法执行前后各执行一次,使用@Before 和 @After 两个注解标注的方法会在每个测试方法执行前后都执行一次。
import org.junit.*;
public class StandardTest {
@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 testCase1() {
System.out.println("in test case 1");
}
@Test
public void testCase2() {
System.out.println("测试生命周期");
}
}
这里有如下几点注意事项:
- 父类的@BeforeClass注解方法会在子类的@BeforeClass注解方法执行前执行。
- 父类@Before修饰的方法会在子类@Before修饰的方法执行前执行。
- 父类@After修饰的方法会在子类@After修饰的方法执行后执行。
- 父类中的被@AfterClass注解方法修饰的方法会在子类的@AfterClass注解修饰的方法执行之后才会被执行。
断言测试注解如下表所示:
断言 | 描述 |
---|---|
void assertEquals() | 如果比较的两个对象是相等的,此方法将正常返回,否则测试将中止 |
void assertTrue() | 断言if条件或变量是否是true |
void assertFalse() | 断言if条件或变量是否是false |
void assertNotNull() | 断言一个对象不为空(null) |
void assertNull() | 断言一个对象为空(null) |
void assertSame() | 断言两个对象引用相同的对象 |
void assertNotSame() | 断言两个对象不是引用同一个对象 |
void assertArrayEquals() | 比较两个数组,如果它们相等,则该方法将继续进行不会发出错误,否则中止测试 |
import org.hamcrest.core.CombinableMatcher;
import org.junit.Test;
import java.util.Arrays;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
public class AssertionTest {
@Test
public void testAssertArrayEquals() {
byte[] expected = "trial".getBytes();
byte[] actual = "trial".getBytes();
assertArrayEquals("failure - byte arrays not same", expected, actual);
}
@Test
public void testAssertEquals() {
assertEquals("failure - strings are not equal", "text", "text");
}
@Test
public void testAssertFalse() {
assertFalse("failure - should be false", false);
}
@Test
public void testAssertNotNull() {
assertNotNull("should not be null", new Object());
}
@Test
public void testAssertNotSame() {
assertNotSame("should not be same Object", new Object(), new Object());
}
@Test
public void testAssertNull() {
assertNull("should be null", null);
}
@Test
public void testAssertSame() {
Integer aNumber = Integer.valueOf(768);
assertSame("should be same", aNumber, aNumber);
}
@Test
public void testAssertThatBothContainsString() {
assertThat("albumen", both(containsString("a")).and(containsString("b")));
}
@Test
public void testAssertThatHasItems() {
assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
}
@Test
public void testAssertThatEveryItemContainsString() {
assertThat(Arrays.asList(new String[]{"fun", "ban", "net"}), everyItem(containsString("n")));
}
@Test
public void testAssertThatHamcrestCoreMatchers() {
assertThat("good", allOf(equalTo("good"), startsWith("good")));
assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
assertThat(7, not(CombinableMatcher.<Integer>either(equalTo(3)).or(equalTo(4))));
assertThat(new Object(), not(sameInstance(new Object())));
}
@Test
public void testAssertTrue() {
assertTrue("failure - should be true", true);
}
}
为保证单元测试的严谨性,通常会模拟不同的测试数据来测试方法的处理能力,为此我们需要编写大量的单元测试的方法。可是这些测试方法都是大同小异的,它们的代码结构都是相同的,不同的仅仅是测试数据和期望值。为解决这个问题,Junit 4 引入了一个新的功能参数化测试,允许开发人员使用不同的值反复运行同一个测试。参数化测试主要解决一次性进行多个测试用例的测试。其主要思想是,将多个测试用例按照,{输入值,输出值}(输入值可以是多个)的列表方式进行测试。
要进行参数化测试,需要在类上面指定如下的运行器:@RunWith (Parameterized.class)
,然后,在提供数据的方法上加上一个@Parameters注解,这个方法必须是静态static的,并且返回一个集合Collection。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.*;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class MyJUnit4ParaTest {
private int input1;
private int input2;
private int expected;
/**
* 准备数据。数据的准备需要在一个方法中进行,该方法需要满足一定的要求:
* 1)该方法必须由Parameters注解修饰
* 2)该方法必须为public static的
* 3)该方法必须返回Collection类型
* 4)该方法的名字不做要求
* 5)该方法没有参数
*/
@Parameterized.Parameters
public static Collection<Object[]> prepareData() {
Object[][] object = {{3, 1, 4}, {36, 6, 42}, {0, 4, 4}};
return Arrays.asList(object);
}
/**
* 构造方法
*
* 必须要为类的所有字段赋值,不管是不是都用到!否则,Junit会出错。
*/
public MyJUnit4ParaTest(int input1, int input2, int expected) {
this.input1 = input1;
this.input2 = input2;
this.expected = expected;
}
@Test
public void testDiv() {
Calculator calc = new Calculator();
// input1,input2,expected分别对应二维数组元素的第一个,第二个和第三个元素
int result = calc.add(input1, input2);
assertEquals(expected, result);
}
}
class Calculator {
public int add(int a, int b) {
return a + b;
}
}
一般来说,在一个类里面只执行一个测试方法。因为所准备的数据是无法共用的,这就要求,所要测试的方法是大数据量的方法,所以才有必要写一个参数化测试。而在实际开发中,参数化测试用到的并不是特别多。
到这里,想必你对 JUnit 4 也有了基本的了解和掌握,都说单元测试是提升软件质量,提升研发效率的必备环节,从会用 JUnit 4 写单元测试开始,培养写测试代码的习惯,在不断实践中提升自身的开发效率,让写出来的代码有更质量的保证。