标签:
单元测试基础
当今软件测试十分盛行时,本人通过项目实践和个人亲身体会浅谈单元测试,本人一直坚持“用代码说话的原则”,同时也希望个人能给出宝贵意见,共同探讨、共同进步,为中国软件事业有更大的发展共同奋斗!
最早我们项目组开发的项目时,写代码都是从底层一直写到表现层到jsp,然后开发人员在web层调试页面,近乎98%都会报一大堆exception,然后再在代码中加断点一步一步查到底哪一层代码出现问题……,比较好点做法就是在各个类中加上main方法测试,但总体很不理想,给web层开发人员的调试和质量控制人员带来繁重的工作压力;使用单元测试后,针对每一个方法都做严格的把关,大大减少调试的时间;同时质量控制人员返回过来的bug少了近60%,现在对于开发人员写测试用例非常熟练,并且本人根据实际情况对测试用例做了点小小改动(这部分主要在后面代码中详述),带来很好的效果!
单元测试到底给实际开发带来什么好处那?
(1)首先对于开发人员来说大大减少调试工作的时间,同时也规范了对于代码安全管理(我们知道那些方法是可以调用的);
(2) 对于整个项目来说,有了完整的测试,保证项目最后交付测试有了可靠依据;
(3)对于测试人员大大减少bug的反馈;
(4)对于项目经理整个项目达到很好的可控;
(5)最主要的完整的单元测试给后期维护人员带来很大的便捷!
单元测试好处可能还有很多,但本人只能理解和感悟这么多,希望观者补充!
单元测试配置:
我将使用eclipse+myEclopse给大家介绍关于JUNIT的环境的简单配置;右键点击项目选择“属性”,在弹出窗口中到环境变量中添加junit.jar包,这样下一步我们就可以进行单元测试了;
使用eclipse快速开发test Case:
如下图:右键选择你要测试的类,在新建中点击“JUnit 测试用例”,
弹出对话框,配置测试名称和根目录,添加注释等,再点击“下一步”到下图:
选择你要测试类中的方法,点击完成!便生成测试类的基本框架,如下代码,我们以对一个DAO类测试为例:
/**//*
* Copyright reserved 2005 by XXXXCo. Ltd.
* Author:Fu wei Date:2006-9-4
*/
import junit.framework.TestCase;
/** *//**
* @author Fu wei
*/
public class OrgTypeDAOTest extends TestCase ...{
/** *//**
* @param arg0
*/
public OrgTypeDAOTest(String arg0) ...{
super(arg0);
}
/**//*
* @see junit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception ...{
super.setUp();
}
/**//*
* @see junit.framework.TestCase#tearDown()
*/
protected void tearDown() throws Exception ...{
super.tearDown();
}
/** *//**
* 主函数
* @param args
*/
public static void main(String[] args)...{
TestRunner.run(OrgTypeDAOTest .class);
}
/** *//**
* {@link OrgTypeDAO#getOrgTypeList()} 的测试方法。
*/
public final void testGetOrgTypeList() ...{
fail("尚未实现"); // TODO
}
/** *//**
* {@link OrgTypeDAO#insertOrgTypeInfo(com.zhjy.mltx.vo.OrgTypeVO)} 的测试方法。
*/
public final void testInsertOrgTypeInfo() ...{
fail("尚未实现"); // TODO
}
/** *//**
* {@link OrgTypeDAO#deleteOrgTypeInfo(java.lang.String)} 的测试方法。
*/
public final void testDeleteOrgTypeInfo() ...{
fail("尚未实现"); // TODO
}
/** *//**
* {@link OrgTypeDAO#updateOrgTypeInfo(com.zhjy.mltx.vo.OrgTypeVO)} 的测试方法。
*/
public final void testUpdateOrgTypeInfo() ...{
fail("尚未实现"); // TODO
}
/** *//**
* {@link OrgTypeDAO#getOrgTypeInfoById(java.lang.String)} 的测试方法。
*/
public final void testGetOrgTypeInfoById() ...{
fail("尚未实现"); // TODO
}
/** *//**
* {@link OrgTypeDAO#isRepeatOrgTypeInfo(java.lang.String)} 的测试方法。
*/
public final void testIsRepeatOrgTypeInfoString() ...{
fail("尚未实现"); // TODO
}
/** *//**
* {@link OrgTypeDAO#isRepeatOrgTypeInfo(com.zhjy.mltx.vo.OrgTypeVO)} 的测试方法。
*/
public final void testIsRepeatOrgTypeInfoOrgTypeVO() ...{
fail("尚未实现"); // TODO
}
/** *//**
* {@link OrgTypeDAO#getFlatOrgIdByName(java.lang.String)} 的测试方法。
*/
public final void testGetFlatOrgIdByName() ...{
fail("尚未实现"); // TODO
}
}
JUnit单元测试一共要注意一下几点:
(1)importjunit.framework.TestCase 和 junit.textui.TestRunner;
(2)继承junit.framework.TestCase ;
(3)自行添加一个main方法 中调用TestRunner.run(测试类名.class);
(4)有一个调用super(String)的构造函数;
以上都是JUnit必有的特征,除以上外,我们发现有许多以test开头的方法,而这些方法正是我们要测试的方法,Junti测试其实采用的是断言的方式,只要我们在所有test开头中的方法对数据添加断言方法,同时提供很多断言的方法,
常用断言方法 |
|
assertEquals("失败提示信息","期望数据","测试数据") |
断言获取数据是否与所期望的相等 |
assertNotNull("失败提示信息","测试数据") |
断言获取数据不为null,否则提示错误 |
assertNull("失败提示信息","测试数据") |
断言获取数据是为null,否则提示错误 |
assertTrue("失败提示信息",测试数据blooean值) |
断言获取数据是否为ture,否则提示错误 |
fail("失败提示信息"); |
此方法一般放到异常处,遇到此方法,测试将停止! |
assertSame("失败提示信息","期望数据","测试数据") |
断言获取数据是否与所期望的相同 |
当我们写完所有方法策略后,JUnit测试如下图:
在方法页面中点击右键在“调试方式”或“运行方式”中点击“JUnit 测试”,就运行测试类!
在执行测试类时,执行的大概过程:
(1)先执行构造方法public OrgTypeDAOTest(String arg0) ;
(2)再执行初始化数据方法protected void setUp() ;
(3)再执行以test开头的测试方法;
(4)最后执行protected void tearDown()方法清理对象;
如果测试失败或者错误,将会显示一个红色的亮条;如果测试通过将显示绿色亮条;如下图
这样就把一个整个单元测试操作例子演示完成!
可能对于一个测试类中有多个方法要测试,对于后面看着的确有些困难,因此,我对上测试类进行简单的调整,如下代码:
import junit.framework.TestCase;
import junit.textui.TestRunner;
import com.zhjy.mock.SpringMock;
/** *//**
* 举例测试类
*/
public class OrgTypeDAOTest extends TestCase ...{ //(1)继承TestCase
//private OrgTypeDAO orgTypeDAO;
//private OrgTypeVO orgTypeVO;
//private String id ;
/** *//**
* 构造方法
* @param arg0
*/
public OrgTypeDAOTest(String arg0) ...{
super(arg0);
}
/**//* 初时化方法
* @see junit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception ...{
super.setUp();
//测试初始话数据调用类 orgTypeDAO和 封装数据的对象orgTypeVO
}
/**//* 执行完清理方法
* @see junit.framework.TestCase#tearDown()
*/
protected void tearDown() throws Exception ...{
//清空 对象 ;==null
//orgTypeDAO =null;
//orgTypeVO =null;
super.tearDown();
}
/** *//**
* 主函数
* @param args
*/
public static void main(String[] args)...{
TestRunner.run(OrgTypeDAOTest.class);
}
/** *//**
* 测试方法
* Test method testOrgTypeInfo
*/
public void testOrgTypeInfo() ...{
//添加
String id = insertOrgTypeInfo();
//列表
orgTypeList();
//修改
updateOrgTypeInfo(id);
//查询
selectOrgTypeInfoById(id);
//校验
iExistOrgByOrgTypeId(id);
//测试是否重复数据方法(add)
isRepeatOrgTypeInfo(orgTypeVO.getName(),"");
//获取数据方法(根据名称)
selectOrgTypeIdByName(orgTypeVO.getName());
//删除
deleteOrgTypeInfo(id);
}
/**//*
*添加初始数据
*/
private void setOrgTypeVOAddInfo() ...{
orgTypeVO.setName("add中海测试");
orgTypeVO.setDescription("add中海测试");
orgTypeVO.setStatus("1");
}
/**//*
*添加初始数据
*/
private void setOrgTypeVOUpdateInfo() ...{
//orgTypeVO.setId(id);
orgTypeVO.setName("add中海测试");
orgTypeVO.setDescription("update中海测试");
orgTypeVO.setStatus("1");
}
/**//*
* 新增方法
* Test method for {@link OrgTypeDAO#insertOrgTypeInfo(com.zhjy.mltx.vo.OrgTypeVO)}.
*/
public String insertOrgTypeInfo() ...{
setOrgTypeVOAddInfo();
String id = null;
try...{
id = orgTypeDAO.insertOrgTypeInfo(orgTypeVO);
}catch(Exception e)...{
fail("添加通用组织机构失败!");
}
return id;
}
/**//*
* 更新方法
* Test method for {@link com.zhjy.mltx.dao.OrgTypeDAO#updateOrgTypeInfo(com.zhjy.mltx.vo.OrgTypeVO)}.
*/
public void updateOrgTypeInfo(String id) ...{
setOrgTypeVOUpdateInfo();
orgTypeVO.setId(id);
try...{
orgTypeDAO.updateOrgTypeInfo(orgTypeVO);
}catch(Exception e)...{
assertTrue("修改通用组织机构失败!", false);
}
//查询
orgTypeVO = orgTypeDAO.selectOrgTypeInfoById(id);
assertEquals("修改通用组织机构失败!", orgTypeVO.getDescription(), "update中海测试");
}
/**//*
* 获取数据方法(主健)
* Test method for {@link OrgTypeDAO#selectOrgTypeInfoById(java.lang.String)}.
*/
public void selectOrgTypeInfoById(String id) ...{
orgTypeVO = orgTypeDAO.selectOrgTypeInfoById(id);
assertTrue("无法查看一条通用机构名称信息!",orgTypeVO != null);
assertEquals("添加通用组织机构失败!", orgTypeVO.getName(), "add中海测试");
assertEquals("修改通用组织机构失败!", orgTypeVO.getDescription(), "update中海测试");
}
/**//*
* 测试此通用组织机构是否被引用
* Test method for {@link OrgTypeDAO#iExistOrgByOrgTypeId(java.lang.String)}.
*/
public void iExistOrgByOrgTypeId(String id) ...{
boolean isfalse;
try ...{
isfalse = this.orgTypeDAO.iExistOrgByOrgTypeId(id);
assertFalse("通用组织机构校验错误!",isfalse);
} catch (DataAccessException e) ...{
assertTrue("通用组织机构数据操作错误!!",false);
} catch (ObjectNotFoundException e) ...{
assertTrue("id is null!!",false);
}
}
/**//*
* 删除
* Test method for {@link OrgTypeDAO#deleteOrgTypeInfo(java.lang.String)}.
*/
public void deleteOrgTypeInfo(String id) ...{
this.orgTypeDAO.deleteOrgTypeInfo(id);
orgTypeVO = this.orgTypeDAO.selectOrgTypeInfoById(id);
assertNull("删除通用组织机构失败!", orgTypeVO);
}
/**//*
* 获取数据方法(根据名称)
* Test method for {@link OrgTypeDAO#selectOrgTypeIdByName(java.lang.String)}.
*/
public void selectOrgTypeIdByName(String name) ...{
String orgtypeid = orgTypeDAO.selectOrgTypeIdByName(name);
//assertEquals(orgtypeid, id);
assertNotNull("未查出来通用组织机构ID!",orgtypeid);
}
/**//*
* 测试是否重复数据方法
* Test method for {@link OrgTypeDAO#isRepeatOrgTypeInfo(com.zhjy.mltx.vo.OrgTypeVO)}.
*/
public void isRepeatOrgTypeInfo(String name,String id) ...{
//setOrgTypeVOUpdateInfo();
OrgTypeVO orgtypetest = new OrgTypeVO();
orgtypetest.setId(id);
orgtypetest.setName(name);
boolean isTrue = orgTypeDAO.isRepeatOrgTypeInfo(orgtypetest);
//assertEquals("通用组织机构错误数据",isTrue, false);
assertTrue("通用组织机构错误数据", isTrue);
}
/**//*
* 列表方法
* Test method for {@link com.zhjy.mltx.dao.OrgTypeDAO#orgTypeList()}.
*/
public void orgTypeList() ...{
List list = orgTypeDAO.orgTypeList();
assertNotNull("无法获取通用机构名称列表list is null!",list);
}
}
处理过程:
(1)把所有以test开头的方法中的方法名称前的test去掉(例如把testOrgTypeList()改为orgTypeList()方法);
(2)同时添加一个以test开头的方法,并调用测试方法;这样做主要是因为执行测试时JUnit(除本身特有的方法出外)只执行以test为开头的方法;
(3)同时大家注意到我还把测试初始数据放到两个private方法中private void setOrgTypeVOAddInfo()和private void setOrgTypeVOUpdateInfo()方法中供调用;
大家可能认为很简单,就是这样简单的改动带来很大的方便,大家是否注意到,我以后运行测试用例时,只关心public void testOrgTypeInfo() 方法就行了,因为只要各个方法的断言策略定制完成,一般就不会再改动,因此,后期的回归测试还是验收测试,缩小了我们对测试的关注点,同时对后面写 test suite构建整体测试带来极大方便性!其中的好处相信只有使用才能体会!
说到这,就此暂告一段落!由于时间仓促,书写不规范和关于任何问题处敬请指出,相互探讨,后面将讨论《单元测试在整个项目中的整体构建》,谢谢!后续!
2005 年 10 月 13 日
JUnit 是 Java? 语言事实上的 标准单元测试库。JUnit 4 是该库三年以来最具里程碑意义的一次发布。它的新特性主要是通过采用 Java 5 中的标记(annotation)而不是利用子类、反射或命名机制来识别测试,从而简化测试。在本文中,执着的代码测试人员 Elliotte Harold 以 JUnit 4 为例,详细介绍了如何在自己的工作中使用这个新框架。注意,本文假设读者具有 JUnit 的使用经验。
JUnit 由Kent Beck 和 Erich Gamma 开发,几乎毫无疑问是迄今所开发的最重要的第三方 Java 库。正如 Martin Fowler 所说,“在软件开发领域,从来就没有如此少的代码起到了如此重要的作用”。JUnit 引导并促进了测试的盛行。由于 JUnit,Java 代码变得更健壮,更可靠,bug 也比以前更少。JUnit(它本身的灵感来自 Smalltalk 的 SUnit)衍生了许多 xUnit 工具,将单元测试的优势应用于各种语言。nUnit (.NET)、pyUnit (Python)、CppUnit (C++)、dUnit (Delphi) 以及其他工具,影响了各种平台和语言上的程序员的测试工作。
然而,JUnit 仅仅是一个工具而已。真正的优势来自于 JUnit 所采用的思想和技术,而不是框架本身。单元测试、测试先行的编程和测试驱动的开发并非都要在 JUnit 中实现,任何比较 GUI 的编程都必须用 Swing 来完成。JUnit 本身的最后一次更新差不多是三年以前了。尽管它被证明比大多数框架更健壮、更持久,但是也发现了 bug;而更重要的是,Java 不断在发展。Java 语言现在支持泛型、枚举、可变长度参数列表和注释,这些特性为可重用的框架设计带来了新的可能。
JUnit 的停滞不前并没有被那些想要废弃它的程序员所打败。挑战者包括 Bill Venners 的 Artima SuiteRunner 以及 Cedric Beust 的 TestNG 等。这些库有一些可圈可点的特性,但是都没有达到 JUnit 的知名度和市场占有份额。它们都没有在诸如 Ant、Maven 或 Eclipse 之类的产品中具有广泛的开箱即用支持。所以 Beck 和 Gamma 着手开发了一个新版本的 JUnit,它利用 Java 5 的新特性(尤其是注释)的优势,使得单元测试比起用最初的 JUnit 来说更加简单。用 Beck 的话来说,“JUnit 4 的主题是通过进一步简化 JUnit,鼓励更多的开发人员编写更多的测试。”JUnit 4 尽管保持了与现有 JUnit 3.8 测试套件的向后兼容,但是它仍然承诺是自 JUnit 1.0 以来 Java 单元测试方面最重大的改进。
注意:该框架的改进是相当前沿的。尽管 JUnit 4 的大轮廓很清晰,但是其细节仍然可以改变。这意味着本文是对 JUnit4 抢先看,而不是它的最终效果。
以前所有版本的 JUnit 都使用命名约定和反射来定位测试。例如,下面的代码测试 1+1 等于 2:
import junit.framework.TestCase;
public class AdditionTest extends TestCase {
private int x = 1; private int y = 1;
public void testAddition() { int z = x + y; assertEquals(2, z); }
} |
而在 JUnit 4 中,测试是由 @Test 注释来识别的,如下所示:
import org.junit.Test; import junit.framework.TestCase;
public class AdditionTest extends TestCase {
private int x = 1; private int y = 1;
@Test public void testAddition() { int z = x + y; assertEquals(2, z); }
} |
使用注释的优点是不再需要将所有的方法命名为 testFoo()、testBar(),等等。例如,下面的方法也可以工作:
import org.junit.Test; import junit.framework.TestCase;
public class AdditionTest extends TestCase {
private int x = 1; private int y = 1;
@Test public void additionTest() { int z = x + y; assertEquals(2, z); }
} |
下面这个方法也同样能够工作:
import org.junit.Test; import junit.framework.TestCase;
public class AdditionTest extends TestCase {
private int x = 1; private int y = 1;
@Test public void addition() { int z = x + y; assertEquals(2, z); }
} |
这允许您遵循最适合您的应用程序的命名约定。例如,我介绍的一些例子采用的约定是,测试类对其测试方法使用与被测试的类相同的名称。例如,List.contains() 由 ListTest.contains() 测试,List.add() 由 ListTest.addAll() 测试,等等。
TestCase 类仍然可以工作,但是您不再需要扩展它了。只要您用 @Test 来注释测试方法,就可以将测试方法放到任何类中。但是您需要导入junit.Assert 类以访问各种 assert 方法,如下所示:
import org.junit.Assert;
public class AdditionTest {
private int x = 1; private int y = 1;
@Test public void addition() { int z = x + y; Assert.assertEquals(2, z); }
} |
您也可以使用 JDK 5 中新特性(static import),使得与以前版本一样简单:
import static org.junit.Assert.assertEquals;
public class AdditionTest {
private int x = 1; private int y = 1;
@Test public void addition() { int z = x + y; assertEquals(2, z); }
} |
这种方法使得测试受保护的方法非常容易,因为测试案例类现在可以扩展包含受保护方法的类了。
SetUp 和 TearDown
JUnit 3 测试运行程序(test runner)会在运行每个测试之前自动调用 setUp() 方法。该方法一般会初始化字段,打开日志记录,重置环境变量,等等。例如,下面是摘自 XOM 的 XSLTransformTest 中的 setUp() 方法:
protected void setUp() {
System.setErr(new PrintStream(new ByteArrayOutputStream()));
inputDir = new File("data"); inputDir = new File(inputDir, "xslt"); inputDir = new File(inputDir, "input");
} |
在 JUnit 4 中,您仍然可以在每个测试方法运行之前初始化字段和配置环境。然而,完成这些操作的方法不再需要叫做 setUp(),只要用 @Before 注释来指示即可,如下所示:
@Before protected void initialize() {
System.setErr(new PrintStream(new ByteArrayOutputStream()));
inputDir = new File("data"); inputDir = new File(inputDir, "xslt"); inputDir = new File(inputDir, "input");
} |
甚至可以用 @Before 来注释多个方法,这些方法都在每个测试之前运行:
@Before protected void findTestDataDirectory() { inputDir = new File("data"); inputDir = new File(inputDir, "xslt"); inputDir = new File(inputDir, "input"); }
@Before protected void redirectStderr() { System.setErr(new PrintStream(new ByteArrayOutputStream())); } |
清除方法与此类似。在 JUnit 3 中,您使用 tearDown() 方法,该方法类似于我在 XOM 中为消耗大量内存的测试所使用的方法:
protected void tearDown() { doc = null; System.gc(); } |
对于 JUnit 4,我可以给它取一个更自然的名称,并用 @After 注释它:
@After protected void disposeDocument() { doc = null; System.gc(); } |
与 @Before 一样,也可以用 @After 来注释多个清除方法,这些方法都在每个测试之后运行。
最后,您不再需要在超类中显式调用初始化和清除方法,只要它们不被覆盖即可,测试运行程序将根据需要自动为您调用这些方法。超类中的 @Before 方法在子类中的 @Before 方法之前被调用(这反映了构造函数调用的顺序)。@After 方法以反方向运行:子类中的方法在超类中的方法之前被调用。否则,多个@Before 或 @After 方法的相对顺序就得不到保证。
JUnit 4 也引入了一个 JUnit 3 中没有的新特性:类范围的 setUp() 和 tearDown() 方法。任何用 @BeforeClass 注释的方法都将在该类中的测试方法运行之前刚好运行一次,而任何用 @AfterClass 注释的方法都将在该类中的所有测试都运行之后刚好运行一次。
例如,假设类中的每个测试都使用一个数据库连接、一个网络连接、一个非常大的数据结构,或者还有一些对于初始化和事情安排来说比较昂贵的其他资源。不要在每个测试之前都重新创建它,您可以创建它一次,并还原它一次。该方法将使得有些测试案例运行起来快得多。例如,当我测试调用第三方库的代码中的错误处理时,我通常喜欢在测试开始之前重定向 System.err,以便输出不被预期的错误消息打乱。然后我在测试结束后还原它,如下所示:
// This class tests a lot of error conditions, which // Xalan annoyingly logs to System.err. This hides System.err // before each test and restores it after each test. private PrintStream systemErr;
@BeforeClass protected void redirectStderr() { systemErr = System.err; // Hold on to the original value System.setErr(new PrintStream(new ByteArrayOutputStream())); }
@AfterClass protected void tearDown() { // restore the original value System.setErr(systemErr); } |
没有必要在每个测试之前和之后都这样做。但是一定要小心对待这个特性。它有可能会违反测试的独立性,并引入非预期的混乱。如果一个测试在某种程度上改变了 @BeforeClass 所初始化的一个对象,那么它有可能会影响其他测试的结果。它有可能在测试套件中引入顺序依赖,并隐藏 bug。与任何优化一样,只在剖析和基准测试证明您具有实际的问题之后才实现这一点。这就是说,我看到了不止一个测试套件运行时间如此之长,以至不能像它所需要的那样经常运行,尤其是那些需要建立很多网络和数据库连接的测试。(例如,LimeWire 测试套件运行时间超过两小时。)要加快这些测试套件,以便程序员可以更加经常地运行它们,您可以做的就是减少 bug。
异常测试是 JUnit 4 中的最大改进。旧式的异常测试是在抛出异常的代码中放入 try 块,然后在 try 块的末尾加入一个 fail() 语句。例如,该方法测试被零除抛出一个 ArithmeticException:
public void testDivisionByZero() {
try { int n = 2 / 0; fail("Divided by zero!"); } catch (ArithmeticException success) { assertNotNull(success.getMessage()); }
} |
该方法不仅难看,而且试图挑战代码覆盖工具,因为不管测试是通过还是失败,总有一些代码不被执行。在 JUnit 4 中,您现在可以编写抛出异常的代码,并使用注释来声明该异常是预期的:
@Test(expected=ArithmeticException.class) public void divideByZero() { int n = 2 / 0; } |
如果该异常没有抛出(或者抛出了一个不同的异常),那么测试就将失败。但是如果您想要测试异常的详细消息或其他属性,则仍然需要使用旧式的 try-catch 样式。
也许您有一个测试运行的时间非常地长。不是说这个测试应该运行得更快,而是说它所做的工作从根本上比较复杂或缓慢。需要访问远程网络服务器的测试通常都属于这一类。如果您不在做可能会中断该类测试的事情,那么您可能想要跳过运行时间长的测试方法,以缩短编译-测试-调试周期。或者也许是一个因为超出您的控制范围的原因而失败的测试。例如,W3C XInclude 测试套件测试 Java 还不支持的一些 Unicode 编码的自动识别。不必老是被迫盯住那些红色波浪线,这类测试可以被注释为 @Ignore,如下所示:
// Java doesn‘t yet support // the UTF-32BE and UTF32LE encodings @Ignore public void testUTF32BE() throws ParsingException, IOException, XIncludeException {
File input = new File( "data/xinclude/input/UTF32BE.xml" ); Document doc = builder.build(input); Document result = XIncluder.resolve(doc); Document expectedResult = builder.build( new File(outputDir, "UTF32BE.xml") ); assertEquals(expectedResult, result);
} |
测试运行程序将不运行这些测试,但是它会指出这些测试被跳过了。例如,当使用文本界面时,会输出一个“I”(代表 ignore),而不是为通过的测试输出所经历的时间,也不是为失败的测试输出“E”:
$ java -classpath .:junit.jar org.junit.runner.JUnitCore nu.xom.tests.XIncludeTest JUnit version 4.0rc1 .....I.. Time: 1.149
OK (7 tests) |
但是一定要小心。最初编写这些测试可能有一定的原因。如果永远忽略这些测试,那么它们期望测试的代码可能会中断,并且这样的中断可能不能被检测到。忽略测试只是一个权宜之计,不是任何问题的真正解决方案。
测试性能是单元测试最为痛苦的方面之一。JUnit 4 没有完全解决这个问题,但是它对这个问题有所帮助。测试可以用一个超时参数来注释。如果测试运行的时间超过指定的毫秒数,则测试失败。例如,如果测试花费超过半秒时间去查找以前设置的一个文档中的所有元素,那么该测试失败:
@Test(timeout=500) public void retrieveAllElementsInDocument() { doc.query("//*"); } |
除了简单的基准测试之外,时间测试也对网络测试很有用。在一个测试试图连接到的远程主机或数据库宕机或变慢时,您可以忽略该测试,以便不阻塞所有其他的测试。好的测试套件执行得足够快,以至程序员可以在每个测试发生重大变化之后运行这些测试,有可能一天运行几十次。设置一个超时使得这一点更加可行。例如,如果解析 http://www.ibiblio.org/xml 花费了超过 2 秒,那么下面的测试就会超时:
@Test(timeout=2000) public void remoteBaseRelativeResolutionWithDirectory() throws IOException, ParsingException { builder.build("http://www.ibiblio.org/xml"); } |
JUnit 4 为比较数组添加了两个 assert() 方法:
public static void assertEquals(Object[] expected, Object[] actual) public static void assertEquals(String message, Object[] expected, Object[] actual) |
这两个方法以最直接的方式比较数组:如果数组长度相同,且每个对应的元素相同,则两个数组相等,否则不相等。数组为空的情况也作了考虑。
JUnit 4 基本上是一个新框架,而不是旧框架的升级版本。JUnit 3 开发人员可能会找到一些原来没有的特性。
最明显的删节就是 GUI 测试运行程序。如果您想在测试通过时看到赏心悦目的绿色波浪线,或者在测试失败时看到令人焦虑的红色波浪线,那么您需要一个具有集成 JUnit 支持的 IDE,比如Eclipse。不管是 Swing 还是 AWT 测试运行程序都不会被升级或捆绑到 JUnit 4 中。
下一个惊喜是,失败(assert 方法检测到的预期的错误)与错误(异常指出的非预期的错误)之间不再有任何差别。尽管 JUnit 3 测试运行程序仍然可以区别这些情况,而 JUnit 4 运行程序将不再能够区分。
最后,JUnit 4 没有 suite() 方法,这些方法用于从多个测试类构建一个测试套件。相反,可变长参数列表用于允许将不确定数量的测试传递给测试运行程序。
我对消除了 GUI 测试运行程序并不感到太高兴,但是其他更改似乎有可能增加 JUnit 的简单性。只要考虑有多少文档和 FAQ 当前专门用于解释这几点,然后考虑对于 JUnit 4,您不再需要解释这几点了。
当前,还没有 JUnit 4 的库版本。如果您想要体验新的版本,那么您需要从 SourceForge上的 CVS 知识库获取它。分支(branch)是“Version4”(参见参考资料)。注意,很多的文档没有升级,仍然是指以旧式的 3.x 方式做事。Java 5 对于编译 JUnit 4 是必需的,因为 JUnit 4 大量用到注释、泛型以及 Java 5 语言级的其他特性。
自 JUnit 3 以来,从命令行运行测试的语法发生了一点变化。您现在使用org.junit.runner.JUnitCore 类:
$ java -classpath .:junit.jar org.junit.runner.JUnitCore TestA TestB TestC... JUnit version 4.0rc1
Time: 0.003
OK (0 tests) |
Beck 和Gamma 努力维持向前和向后兼容。JUnit 4 测试运行程序可以运行 JUnit 3 测试,不用做任何更改。只要将您想要运行的每个测试的全限定类名传递给测试运行程序,就像针对 JUnit 4 测试一样。运行程序足够智能,可以分辨出哪个测试类依赖于哪个版本的JUnit,并适当地调用它。
向后兼容要困难一些,但是也可以在 JUnit 3 测试运行程序中运行 JUnit 4 测试。这一点很重要,所以诸如 Eclipse 之类具有集成 JUnit 支持的工具可以处理 JUnit 4,而不需要更新。为了使 JUnit 4 测试可以运行在 JUnit 3 环境中,可以将它们包装在 JUnit4TestAdapter 中。将下面的方法添加到您的 JUnit 4 测试类中应该就足够了:
public static junit.framework.Test suite() { return new JUnit4TestAdapter(AssertionTest.class); } |
但是由于 Java 比较多变,所以 JUnit 4 一点都不向后兼容。JUnit 4 完全依赖于 Java 5 特性。对于 Java 1.4 或更早版本,它将不会编译或运行。
JUnit 4 远没有结束。很多重要的方面没有提及,包括大部分的文档。我不推荐现在就将您的测试套件转换成注释和 JUnit 4。即使如此,开发仍在快速进行,并且 JUnit 4 前景非常看好。尽管 Java 2 程序员在可预见的未来仍然需要使用 JUnit 3.8,但是那些已经转移到 Java 5 的程序员则应该很快考虑使他们的测试套件适合于这个新的框架,以便匹配。
junit教材
几乎所有程序员都听说过Junit的大名,但不知真正懂得运用它的人有多少,我便是其中的一个小白。
知道Junit是用来测试的,但却把“宝刀”当成了“菜刀”用。为了从此不再菜鸟,特此总结整理了下Junit的知识点。
一、建立Junit测试类
1. 右击test测试包,选择New-->Oher...
2. 在窗口中找到Junit,选择Junit Test Case
3. 输入名称(Name),命名规则一般建议采用:类名+Test。Browse...选择要测试的类,这里是StudentService。
4. 勾选要测试的方法
5. 生成后,效果如下:
这里import static是引入Assert类中静态属性或静态方法的写法。原来要Assert.fail(),现在只需直接fial()即可,即省略了Assert。
其实不通过Junit新建向导来建立也可以,随便建立一个新类后,只需在方法上加入@Test注解即可。
二、核心——断言
断言是编写测试用例的核心实现方式,即期望值是多少,测试的结果是多少,以此来判断测试是否通过。
1. 断言核心方法
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() |
让测试失败 |
2. 示例
三、核心——注解
1. 说明
@Before |
初始化方法 |
@After |
释放资源 |
@Test |
测试方法,在这里可以测试期望异常和超时时间 |
@Ignore |
忽略的测试方法 |
@BeforeClass |
针对所有测试,只执行一次,且必须为static void |
@AfterClass |
针对所有测试,只执行一次,且必须为static void |
@RunWith |
指定测试类使用某个运行器 |
@Parameters |
指定测试类的测试数据集合 |
@Rule |
允许灵活添加或重新定义测试类中的每个测试方法的行为 |
@FixMethodOrder |
指定测试方法的执行顺序 |
2. 执行顺序
一个测试类单元测试的执行顺序为:
@BeforeClass –> @Before –> @Test –> @After –>@AfterClass
每一个测试方法的调用顺序为:
@Before –> @Test –> @After
3. 示例
执行结果:
图中左上红框中部分表示Junit运行结果,5个成功(1个忽略),1个错误,1个失败。(注意错误和失败不是一回事,错误说明代码有错误,而失败表示该测试方法测试失败)
左下红框中则表示出了各个测试方法的运行状态,可以看到成功、错误、失败、失败各自的图标是不一样的,还可以看到运行时间。
右边部分则是异常堆栈,可查看异常信息。
四、实例总结
1. 参数化测试
有时一个测试方法,不同的参数值会产生不同的结果,那么我们为了测试全面,会把多个参数值都写出来并一一断言测试,这样有时难免费时费力,这是我们便可以采用参数化测试来解决这个问题。参数化测试就好比把一个“输入值,期望值”的集合传入给测试方法,达到一次性测试的目的。
@Parameters注解参数name,实际是测试方法名称。由于一个test()方法就完成了所有测试,那假如某一组测试数据有问题,那在Junit的结果页面里该如何呈现?因此采用name实际上就是区分每个测试数据的测试方法名。如下图:
2. 打包测试
同样,如果一个项目中有很多个测试用例,如果一个个测试也很麻烦,因此打包测试就是一次性测试完成包中含有的所有测试用例。
这个功能也需要使用一个特殊的Runner ,需要向@RunWith注解传递一个参数Suite.class 。同时,我们还需要另外一个注解@Suite.SuiteClasses,来表明这个类是一个打包测试类。并将需要打包的类作为参数传递给该注解就可以了。至于AllCaseTest随便起一个类名,内容为空既可。运行AllCaseTest类即可完成打包测试
3. 异常测试
异常测试与普通断言测试不同,共有三种方法,其中最为灵活的是第三种,可以与断言结合使用
第一种:
第二种:
第三种:
在上述几种方法中,无论是expected还是expect都表示期望抛出的异常,假如某一方法,当参数为某一值时会抛出异常,那么使用第一种方法就必须为该参数单独写一个测试方法来测试异常,而无法与其他参数值一同写在一个测试方法里,所以显得累赘。第二种方法虽然解决这个问题,但是写法不仅繁琐也不利于理解。而第三种犯法,不仅能动态更改期望抛出的异常,与断言语句结合的也非常好,因此推荐使用该方法来测试异常。
4. 限时测试
有时为了防止出现死循环或者方法执行过长(或检查方法效率),而需要使用到限时测试。顾名思义,就是超出设定时间即视为测试失败。共有两种写法。
第一种:
第二种:
其中,第二种方法与异常测试的第三种方法的写法类似。也是推荐的写法。
至此,Junit的教程总结性文章已介绍完了。通过系统总结也进一步加深了对Junit的认识,希望也能同样帮助到对Junit还不太理解的朋友。如果大家还有什么好的建议和用法,很欢迎能提出来一起交流。
一、会用Spring测试套件的好处
在开发基于Spring的应用时,如果你还直接使用Junit进行单元测试,那你就错过了Spring为我们所提供的饕餮大餐了。使用Junit直接进行单元测试有以下四大不足:
1)导致多次Spring容器初始化问题
根据JUnit测试方法的调用流程,每执行一个测试方法都会创建一个测试用例的实例并调用setUp()方法。由于一般情况下,我们在setUp()方法中初始化Spring容器,这意味着如果测试用例有多少个测试方法,Spring容器就会被重复初始化多次。虽然初始化Spring容器的速度并不会太慢,但由于可能会在Spring容器初始化时执行加载Hibernate映射文件等耗时的操作,如果每执行一个测试方法都必须重复初始化Spring容器,则对测试性能的影响是不容忽视的;
使用Spring测试套件,Spring容器只会初始化一次
2)需要使用硬编码方式手工获取Bean
在测试用例类中我们需要通过ctx.getBean()方法从Spirng容器中获取需要测试的目标Bean,并且还要进行强制类型转换的造型操作。这种乏味的操作迷漫在测试用例的代码中,让人觉得烦琐不堪;
使用Spring测试套件,测试用例类中的属性会被自动填充Spring容器的对应Bean,无须在手工设置Bean!
3)数据库现场容易遭受破坏
测试方法对数据库的更改操作会持久化到数据库中。虽然是针对开发数据库进行操作,但如果数据操作的影响是持久的,可能会影响到后面的测试行为。举个例子,用户在测试方法中插入一条ID为1的User记录,第一次运行不会有问题,第二次运行时,就会因为主键冲突而导致测试用例失败。所以应该既能够完成功能逻辑检查,又能够在测试完成后恢复现场,不会留下“后遗症”;
使用Spring测试套件,Spring会在你验证后,自动回滚对数据库的操作,保证数据库的现场不被破坏,因此重复测试不会发生问题!
4)不方便对数据操作正确性进行检查
假如我们向登录日志表插入了一条成功登录日志,可是我们却没有对t_login_log表中是否确实添加了一条记录进行检查。一般情况下,我们可能是打开数据库,肉眼观察是否插入了相应的记录,但这严重违背了自动测试的原则。试想在测试包括成千上万个数据操作行为的程序时,如何用肉眼进行检查?
只要你继承Spring的测试套件的用例类,你就可以通过jdbcTemplate(或Dao等)在同一事务中访问数据库,查询数据的变化,验证操作的正确性!
Spring提供了一套扩展于Junit测试用例的测试套件,使用这套测试套件完全解决了以上四个问题,让我们测试Spring的应用更加方便。这个测试套件主要由org.springframework.test包下的若干类组成,使用简单快捷,方便上手。
二、使用方法
1)基本用法
@RunWith(SpringJUnit4ClassRunner.class) 用于配置spring中测试的环境
@ContextConfiguration(locations = {"classpath:config/applicationContext-*.xml","classpath:services/ext/service-*.xml" })用于指定配置文件所在的位置
@Resource注入Spring容器Bean对象,注意与@Autowired区别
2)事务用法
@TransactionConfiguration(transactionManager="transactionManager")读取Spring配置文件中名为transactionManager的事务配置,defaultRollback为事务回滚默认设置。该注解是可选的,可使用@Transactional与@Rollback配合完成事务管理。当然也可以使用@Transactional与@TransactionConfiguration配合。
@Transactional开启事务。可放到类或方法上,类上作用于所有方法。
@Rollback事务回滚配置。只能放到方法上。
3)继承AbstractTransactionalJUnit4SpringContextTests
AbstractTransactionalJUnit4SpringContextTests:这个类为我们解决了在web.xml中配置OpenSessionInview所解决的session生命周期延长的问题,所以要继承这个类。该类已经在类级别预先配置了好了事物支持,因此不必再配置@Transactional和@RunWith
4)继承
5)综合
@BeforeTransaction在事务之前执行
@AfterTransaction在事务之后执行
@NotTransactional不开启
|
|
标签:
原文地址:http://blog.csdn.net/mph987/article/details/51217748