java单元测试JUnit框架原理与用法实例教程
本文实例讲述了java单元测试junit框架原理与用法。分享给大家供大家参考,具体如下:
1 简介
junit是一个java语言的单元测试框架,它由 kent beck 和 erich gamma 建立,逐渐成为 xunit 家族中最为成功的一个。 junit有它自己的junit扩展生态圈,多数java的开发环境都已经集成了junit作为单元测试的工具。在这里,一个单元可以是一个方法、类、包或者子系统。因此,单元测试是指对代码中的最小可测试单元进行检查和验证,以便确保它们正常工作。例如,我们可以给予一定的输入测试输出是否是所希望得到的结果。在本篇博客中,作者将着重介绍 junit 4.x 版本的特性,这也是我们在日常开发中使用最多的版本。
2 特点
junit提供了注释以及确定的测试方法;
junit提供了断言用于测试预期的结果;
junit测试优雅简洁不需要花费太多的时间;
junit测试让大家可以更快地编写代码并且提高质量;
junit测试可以组织成测试套件包含测试案例,甚至其他测试套件;
junit显示测试进度,如果测试是没有问题条形是绿色的,测试失败则会变成红色;
junit测试可以自动运行,检查自己的结果,并提供即时反馈,没有必要通过测试结果报告来手动梳理。
3 内容
3.1 注解
@test :该注释表示,用其附着的公共无效方法(即用public修饰的void类型的方法 )可以作为一个测试用例;
@before :该注释表示,用其附着的方法必须在类中的每个测试之前执行,以便执行测试某些必要的先决条件;
@beforeclass :该注释表示,用其附着的静态方法必须执行一次并在类的所有测试之前,发生这种情况时一般是测试计算共享配置方法,如连接到数据库;
@after :该注释表示,用其附着的方法在执行每项测试后执行,如执行每一个测试后重置某些变量,删除临时变量等;
@afterclass :该注释表示,当需要执行所有的测试在junit测试用例类后执行,afterclass注解可以使用以清理建立方法,如断开数据库连接,注意:附有此批注(类似于beforeclass)的方法必须定义为静态;
@ignore :该注释表示,当想暂时禁用特定的测试执行可以使用忽略注释,每个被注解为@ignore的方法将不被执行。
/** * junit 注解示例 */ @test public void testyeepay(){ syetem.out.println("用@test标示测试方法!"); } @afterclass public static void paylus(){ syetem.out.println("用@afterclass标示的方法在测试用例类执行完之后!"); }
3.2 断言
在这里,作者将介绍一些断言方法,所有这些方法都来自 org.junit.assert 类,其扩展了 java.lang.object 类并为它们提供编写测试,以便检测故障。简而言之,我们就是通过断言方法来判断实际结果与我们预期的结果是否相同,如果相同,则测试成功,反之,则测试失败。
void assertequals([string message], expected value, actual value) :断言两个值相等,值的类型可以为int、short、long、byte、char 或者
java.lang.object,其中第一个参数是一个可选的字符串消息;
void asserttrue([string message], boolean condition) :断言一个条件为真;
void assertfalse([string message],boolean condition) :断言一个条件为假;
void assertnotnull([string message], java.lang.object object) :断言一个对象不为空(null);
void assertnull([string message], java.lang.object object) :断言一个对象为空(null);
void assertsame([string message], java.lang.object expected, java.lang.object actual) :断言两个对象引用相同的对象;
void assertnotsame([string message], java.lang.object unexpected, java.lang.object actual) :断言两个对象不是引用同一个对象;
void assertarrayequals([string message], expectedarray, resultarray) :断言预期数组和结果数组相等,数组的类型可以为int、long、short、char、byte 或者 java.lang.object
4 junit 3.x 和 junit 4.x 的区别
4.1 junit 3.x
(1)使用 junit 3.x 版本进行单元测试时,测试类必须要继承于 testcase 父类;
(2)测试方法需要遵循的原则:
① public的;
② void的;
③ 无方法参数;
④方法名称必须以 test 开头;
(3)不同的测试用例之间一定要保持完全的独立性,不能有任何的关联;
(4)要掌握好测试方法的顺序,不能依赖于测试方法自己的执行顺序。
/** * 用 junit 3.x 进行测试 */ import junit.framework.assert; import junit.framework.testcase; public class testoperation extends testcase { private operation operation; public testoperation(string name) { // 构造函数 super(name); } @override public void setup() throws exception { // 在每个测试方法执行 [之前] 都会被调用,多用于初始化 system.out.println("欢迎使用junit进行单元测试..."); operation = new operation(); } @override public void teardown() throws exception { // 在每个测试方法执行 [之后] 都会被调用,多用于释放资源 system.out.println("junit单元测试结束..."); } public void testdividebyzero() { throwable te = null; try { operation.divide(6, 0); assert.fail("测试失败"); //断言失败 } catch (exception e) { e.printstacktrace(); te = e; } assert.assertequals(exception.class, te.getclass()); assert.assertequals("除数不能为 0 ", te.getmessage()); } }
4.2 junit 4.x
(1)使用 junit 4.x 版本进行单元测试时,不用测试类继承testcase父类;
(2)junit 4.x 版本,引用了注解的方式进行单元测试;
(3)junit 4.x 版本我们常用的注解包括:
@before 注解:与junit 3.x 中的 setup() 方法功能一样,在每个测试方法之前执行,多用于初始化;
@after 注解:与 junit 3.x 中的 teardown() 方法功能一样,在每个测试方法之后执行,多用于释放资源;
@test(timeout = xxx) 注解:设置当前测试方法在一定时间内运行完,否则返回错误;
@test(expected = exception.class) 注解:设置被测试的方法是否有异常抛出。抛出异常类型为:exception.class;
此外,我们可以通过阅读上面的第二部分“2 注解”了解更多的注解。
/** * 用 junit 4.x 进行测试 */ import static org.junit.assert.*; import org.junit.after; import org.junit.afterclass; import org.junit.before; import org.junit.beforeclass; import org.junit.test; public class testoperation { private operation operation; @beforeclass public static void globalinit() { // 在所有方法执行之前执行 system.out.println("@beforeclass标注的方法,在所有方法执行之前执行..."); } @afterclass public static void globaldestory() { // 在所有方法执行之后执行 system.out.println("@afterclass标注的方法,在所有方法执行之后执行..."); } @before public void setup() { // 在每个测试方法之前执行 system.out.println("@before标注的方法,在每个测试方法之前执行..."); operation = new operation(); } @after public void teardown() { // 在每个测试方法之后执行 system.out.println("@after标注的方法,在每个测试方法之后执行..."); } @test(timeout=600) public void testadd() { // 设置限定测试方法的运行时间 如果超出则返回错误 system.out.println("测试 add 方法..."); int result = operation.add(2, 3); assertequals(5, result); } @test public void testsubtract() { system.out.println("测试 subtract 方法..."); int result = operation.subtract(1, 2); assertequals(-1, result); } @test public void testmultiply() { system.out.println("测试 multiply 方法..."); int result = operation.multiply(2, 3); assertequals(6, result); } @test public void testdivide() { system.out.println("测试 divide 方法..."); int result = 0; try { result = operation.divide(6, 2); } catch (exception e) { fail(); } assertequals(3, result); } @test(expected = exception.class) public void testdivideagain() throws exception { system.out.println("测试 divide 方法,除数为 0 的情况..."); operation.divide(6, 0); fail("test error"); } public static void main(string[] args) { } }
4.3 特别提醒
通过以上两个例子,我们已经可以大致知道 junit 3.x 和 junit 4.x 两个版本的区别啦!首先,如果我们使用 junit 3.x,那么在我们写的测试类的时候,一定要继承 testcase 类,但是如果我们使用 junit 4.x,则不需继承 testcase 类,直接使用注解就可以啦!在 junit 3.x 中,还强制要求测试方法的命名为“ testxxxx ”这种格式;在 junit 4.x 中,则不要求测试方法的命名格式,但作者还是建议测试方法统一命名为“ testxxxx ”这种格式,简洁明了。
此外,在上面的两个示例中,我们只给出了测试类,但是在这之前,还应该有一个被测试类,也就是我们真正要实现功能的类。现在,作者将给出上面示例中被测试的类,即 operation 类:
/** * 定义了加减乘除的法则 */ public class operation { public static void main(string[] args) { system.out.println("a + b = " + add(1,2)); system.out.println("a - b = " + subtract(1,2)); system.out.println("a * b = " + multiply(1,2)); system.out.println("a / b = " + divide(4,2)); system.out.println("a / b = " + divide(1,0)); } public static int add(int a, int b) { return a + b; } public static int subtract(int a, int b) { return a - b; } public static int multiply(int a, int b) { return a * b; } public static int divide(int a, int b) { return a / b; } }
5 测试示例
5.1 示例一:简单的 junit 3.x 测试
import junit.framework.test; import junit.framework.testcase; import junit.framework.testsuite; import java.util.arraylist; import java.util.collection; /** * 1、创建一个测试类,继承testcase类 */ public class simpletestdemo extends testcase { public simpletestdemo(string name) { super(name); } /** * 2、写一个测试方法,断言期望的结果 */ public void testemptycollection(){ collection collection = new arraylist(); asserttrue(collection.isempty()); } /** * 3、写一个suite()方法,它会使用反射动态的创建一个包含所有的testxxxx方法的测试套件 */ public static test suit(){ return new testsuite(simpletestdemo.class); } /** * 4、写一个main()方法,以文本运行器的方式方便的运行测试 */ public static void main(string[] args) { junit.textui.testrunner.run(suit()); } }
5.2 示例二:套件测试
首先,介绍一下套件测试,简单来讲,测试套件是指:一些测试不同类的用例,可以使用 @runwith 和 @suite 注解把所有的测试类套在一起,从而形成测试套件。如果有很多测试类,想让它们都运行在同一时间,而不是单一地运行每个测试,套件测试是非常有用的。当一个类被注解为 @runwith, junit 将调用其中的注解,以便运行测试类,而不使用内置的 junit 运行方法。
/** * 待测试类 */ import java.util.arrays; public class gotowork { public string[] prepareskills() { string[] skill = { "java", "mysql", "jsp" }; system.out.println("my skills include : " + arrays.tostring(skill)); return skill; } public string[] addskills() { string[] skill = { "java", "mysql", "jsp", "junit" }; system.out.println("look, my skills include : " + arrays.tostring(skill)); return skill; } }
/** * 测试类 1 */ import org.junit.test; import static org.junit.assert.*; public class prepareskillstest { gotowork gotowork = new gotowork(); string[] skill = { "java", "mysql", "jsp" }; @test public void testprepareskills() { system.out.println("inside testprepareskills()"); assertarrayequals(skill, gotowork.prepareskills()); } }
/** * 测试类 2 */ import org.junit.test; import static org.junit.assert.*; public class addskillstest { gotowork gotowork = new gotowork(); string[] skill = { "java", "mysql", "jsp", "junit" }; @test public void testaddskills() { system.out.println("inside testaddpencils()"); assertarrayequals(skill, gotowork.addskills()); } }
/** * 套件测试 */ import org.junit.runner.runwith; import org.junit.runners.suite; @runwith(suite.class) @suite.suiteclasses({ prepareskillstest.class, addskillstest.class }) public class suittest { }
使用 @suite.suiteclasses 注解,可以定义测试类,将被列入执行,并且执行的顺序就是在 @suite.suiteclasses 注解中定义的顺序。
5.3 示例三:参数化测试
首先介绍一下参数化测试,一个测试类可以被看作是一个参数化测试类,当其满足下列所有要求:
① 该类被注解为 @runwith(parameterized.class);
② 该类有一个构造函数,存储测试数据;
③ 该类有一个静态方法生成并返回测试数据,并标注 @parameters 注解;
④ 该类有一个测试方法,即用注解 @test 标注的方法。
/** * 待测试类 */ public class calculate { public int sum(int var1, int var2) { system.out.println("此方法的参数值分别为 : " + var1 + " + " + var2); return var1 + var2; } }
/** * 参数化测试类 */ import static org.junit.assert.assertequals; import java.util.arrays; import java.util.collection; import org.junit.test; import org.junit.runner.runwith; import org.junit.runners.parameterized; import org.junit.runners.parameterized.parameters; @runwith(parameterized.class) public class calculatetest { private int expected; private int first; private int second; public calculatetest(int expectedresult, int firstnumber, int secondnumber) { this.expected = expectedresult; this.first = firstnumber; this.second = secondnumber; } @parameters public static collection addednumbers() { return arrays.aslist(new integer[][] { { 3, 1, 2 }, { 5, 2, 3 }, { 7, 3, 4 }, { 9, 4, 5 }, }); } @test public void testsum() { calculate add = new calculate(); system.out.println("addition with parameters : " + first + " and " + second); assertequals(expected, add.sum(first, second)); } }
观察 calculatetest 类,它满足上述所有的要求,因此它就可以称为一个参数化测试类。addednumbers 方法使用注释 @parameters 返回数组的集合,每个数组包括每个测试执行输入和输出数字,每个数组中的元素数必须相同好与构造参数的个数相匹配。所以,在这种特定的情况下,每个数组包括三个元素,即表示要加入的两个元素和一个结果元素。
6 个人建议
有些童鞋可能会有一些误解,认为写测试代码没有用,而且还会增大自己的压力,浪费时间。但事实上,写测试代码与否,还是有很大区别的,如果是在小的项目中,或许这种区别还不太明显,但如果在大型项目中,一旦出现错误或异常,用人力去排查的话,那将会浪费很多时间,而且还不一定排查的出来,但是如果用测试代码的话,junit 就是自动帮我们判断一些代码的结果正确与否,从而节省的时间将会远远超过你写测试代码的时间。
因此,个人建议:要养成编写测试代码的习惯,码一点、测一点;再码一点,再测一点,如此循环。在我们不断编写与测试代码的过程中,我们将会对类的行为有一个更为深入的了解,从而可以有效的提高我们的工作效率。下面,作者就给出一些具体的编写测试代码的技巧和较好的实践方法:
1. 不要用 testcase 的构造函数初始化 fixture,而要用 setup() 和 teardown() 方法;
2. 不要依赖或假定测试运行的顺序,因为 junit 会利用 vector 保存测试方法,所以不同的平台会按不同的顺序从 vector 中取出测试方法;
3. 避免编写有副作用的 testcase,例如:如果随后的测试依赖于某些特定的交易数据,就不要提交交易数据,只需要简单的回滚就可以了;
4. 当继承一个测试类时,记得调用父类的 setup() 和 teardown() 方法;
5. 将测试代码和工作代码放在一起,同步编译和更新;
6. 测试类和测试方法应该有一致的命名方案,如在工作类名前加上 test 从而形成测试类名;
7. 确保测试与时间无关,不要使用过期的数据进行测试,以至于导致在随后的维护过程中很难重现测试;
8. 如果编写的软件面向国际市场,那么编写测试时一定要考虑国际化的因素;
9. 尽可能地利用 junit 提供地 assert 和 fail 方法以及异常处理的方法,其可以使代码更为简洁;
10. 测试要尽可能地小,执行速度快;
11. 不要硬性规定数据文件的路径;
12. 使用文档生成器做测试文档。
事实上,在 junit 中使用 try catch 来捕获异常是没有必要的,因为 junit 会自动捕获异常,那些没有被捕获的异常就会被当成错误处理啦!
更多关于java相关内容感兴趣的读者可查看本站专题:《java数据结构与算法教程》、《java字符与字符串操作技巧总结》、《java操作dom节点技巧总结》、《java文件与目录操作技巧汇总》和《java缓存操作技巧汇总》
希望本文所述对大家java程序设计有所帮助。