为何要编写单元测试?
企业开发当中,无论大小项目都离不开测试,包括单元测试,回归测试,性能测试等等等等,而单元测试就是来验证程序员写代码是否正确的有效手段,在日常开发当中编写单元测试是非常有必要的,试想一下若然正在开发一个庞大的项目,若然编写的每个业务逻辑都靠部署到服务器运行程序通过前台界面点击来进行测试的话,第一:效率非常低下,不自动化,第二:若然某一天某处加入了新代码出现了bug,查找出錯誤要費很大的功夫,所以笔者建议开发者要熟练的掌握单元测试的编写
什么是Junit?
Junit是java领域占有率非常高的一个单元测试框架,已几近成为了单元测试的标准,当然JUnit还存在一定的缺点,本文先从JUnit的简单使用开始
单元测试的编写原则:
1.在eclipse中创建1个source folder命名为test(使用Maven后已要求创建)
2.测试类所在的包要求和被测试类的包一致
3.测试类要使用Test作为开头或结尾,例如 --> UserServiceTest
4.测试类的每个方法,都必须是可以独立执行的,不存在顺序或依赖
JUnit3和JUnit4的区别:
JUnit3中,测试类都需要继承TestCase,而且测试类需要使用testXXX来作为开头,若然希望在测试方法前运行某个初始化方法,这个方法的名称必须是setUp,在测试方法后运行某个方法名称必须是tearDown
在JUnit4中不需要继承TestCase,而是使用了更为方便的annotation,只需要使用@Test来表示,测试前初始化方法使用@Before,测试后释放资源方法使用@After,若只想该方法只执行一次,使用@BeforeClass和@AfterClass
JUnit使用:
由于JUnit3和JUnit4的区别较为明显,所以笔者这里使用JUnit4进行讲解
class="java" name="code">
package com.accentrix.ray;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/*
* 测试类最基本的格式
*/
public class TestUser {
@BeforeClass
public static void init() {
/*
* 此方法只会在运行所有单元测试前执行一次,
* 通常用來获取数据库连接,Spring管理的Bean等等
*/
}
@Before
public void setUp() {
/*
* 此方法运行每个单元测试前都会执行,
* 通常用来准备数据或获取单元测试依赖的数据或对象
*/
}
@Test
public void testAddUser() {
/*
* 测试类的主要方法,在这里写所有的测试业务逻辑
*/
}
@Test(expected = Exception.class)
public void testDleteUser() {
/*
* 请注意注解上的experced,使用该参数代表可以认为
* 这个单元测试方法会抛出Exception的异常,若然没有视为不通过
*/
}
@Test(timeout = 1000)
public void testUpdateUser() {
/*
* 请注意注解上的timeout,使用该参数代表该单元测试需要
* 在1000毫秒内完成,否则视为不通过,可以用做简单的性能测试
*/
}
@After
public void tearDown() {
/*
* 此方法运行每个单元测试后都会执行,
* 主要用来和setUp对应,清理获取的资源
*/
}
@AfterClass
public static void destroy() {
/*
* 此方法会在运行所有单元测试后执行一次,
* 通常用来释放资源,例如数据库连接,IO流等等
*/
}
}
通过上面的代码了解到JUnit单元测试类的基本格式,但所有test的方法体都为空,下面就来看以下如何判断测试类是否成功并正确,通过断言(Assert)的方式判断测试是否通过
package com.accentrix.ray;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
//静态导入
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
public class TestUser {
private Map<Integer, User> userMap = new HashMap<Integer, User>();
// 添加10個用戶
@Before
public void setUp() {
for (int i = 0; i < 10; i++) {
userMap.put(i, new User(i, "123", "456"));
}
}
@Test
public void testGetUser() {
User user = userMap.get(1);
// 判断用户是否为空
// assertNull代表user必須为null才通过测试,第一個参数为提示信息
assertNull("get user is null", user);
// 判断用户是否不为空
// assertNotNull代表user必须不为null才通过参数
assertNotNull("get user is not null", user);
// 判断用户名是否和输入一致
// 第一个参数为提示信息,第二个参数为实际的值,第三个参数为期望的值
String newUsername = "admin";
// assertEquals("用戶名不匹配", newUsername, user.getUsername());
// 判断给定用户名是否包含在所有用户当中
// 若然包含,測試通過
Integer userId = 1;
assertTrue("用戶ID是否包含在其中", userMap.containsKey(userId));
}
// 清空用戶
@After
public void tearDown() {
userMap.clear();
}
}
在开发当中,通常是定义了真实的业务类,然后再创建我们的测试类,当然测试驱动开发例外,这里不多叙述,当我们定义了多个测试方法,但还没提供实现时,可以选择用以下方式来提醒或保证还存在没实现的方法
package com.accentrix.ray;
import static org.junit.Assert.fail;
import org.junit.Ignore;
import org.junit.Test;
package com.accentrix.ray;
import static org.junit.Assert.fail;
import org.junit.Ignore;
import org.junit.Test;
public class TestUser {
@Test
@Ignore
public void testAddUser() {
//添加@Ignore后該方法將被忽略而不去执行
}
@Test
public void testGetUser() {
//当运行测试时会提示该方法还没实现
fail("该方法暂时没提供实现");
}
}
运行结果如上图
如何运行多个单元测试?
从上图可以看到,在运行单元测试的时候都是一个单元测试类这样来运行的,但项目当中可能存在上百个测试类的时候,每次要手动一个一个单元测试的运行简直就是噩梦,以下我们来看一下如何集中运行所有的单元测试
首先创建1个Junit Test Suite,如下图
该类会生成以下的代码
package com.accentrix.ray;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({ TestUser.class })
public class AllTests {
/*
* @RunWith代表是運行Suite的类,固定写法
* @SuiteClasses中接受一个数组,里面为你需要
* 集中运行的测试类的class,以上代码为自动生成
* 当然读者可以通过Maven來运行单元测试,这里不过多叙述
*/
}
总结:
笔者在开发项目的过程当中,其实非常想保持编写JUnit的习惯,但由于项目的规模和时间的不允许,很多情况下都没有编写单元测试,但编写单元测试的益处是非常能体现出来的,若然是较大型的项目,单元测试时非常非常必要,所以建议读者允许的情况下,尽量使用单元测试来测试业务逻辑,当然Junit的能力也是有限的,例如需要结合其他的框架才能达到数据现场的维护和调试Servlet,关于结合其他框架优化JUnit请留意笔者更多文章