简介: DDTUnit 是一个基于JUnit的以数据为驱动方法的测试框架。它通过提供一个简单的 XML 数据描述文件,把测试代码和测试数据剥离开来,进而实现了测试代码与数据无关。同时,该XML文件支持复杂自定义数据对象以及诸如collection,map等集合类。本文以实例探讨的方式深入挖掘DDTUnit的使用,并通过与传统的测试方法进行比较,让读者感受到使用DDTUnit所带来的好处。
DDTUnit,是一个基于 JUnit 的以数据为驱动方法的测试框架。它通过提供一个简单的 XML 数据描述文件,把测试代码和测试数据剥离开来,进而实现了测试代码与数据无关。该 XML 文件支持复杂自定义数据对象以及诸如 Collection,Map 等集合类。
- 提供 DDTTestCase 测试基类,该类继承 JUnit TestCase 类,兼容传统测试方法。
- 提供支持自定义复杂数据对象的 XML Schema 定义。
- 通过 SAX parser 和 XML Schema 定义对用户自定义 XML 数据描述文件进行有效性验证。
- 通过解析 XML 数据描述文件获取用户自定义数据。
- 支持断言对象数据定义以及断言判定。
- 下载 DDTUnit 二进制文件及其依赖库
下载链接:http://ddtunit.sourceforge.net/,当前最新版本是 0.8.8。
图 1. DDTUnit 下载
- 在 RAD 中新建 Java Project 并把 ddtunit-0.8.8.jar 文件导入编译路径。同时 DDTUnit 本身依赖一些 Jar 包,同样需要下载解压并导入工程编译路径,下载地址同上,在此不再多说。
- 新建 XML 文件,命名为 DDT-TestResource.xml 将其存放在工程根目录,并修改其内容如下:
清单 1. DDT-TestResource.xml
<?xml version="1.0" encoding="UTF-8"?> <ddtunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../ddtunit.xsd"> <cluster id="MyClassTest"> <group id="testLocale"> <test id="en"> <objs> <obj id="locale" type="String">en_US</obj> </objs> <asserts> <assert id="result" action="isEqual" type="String">Hello, world!</assert> </asserts> </test> <test id="cn"> <objs> <obj id="locale" type="String">zh_CN</obj> </objs> <asserts> <assert id="result" action="isEqual" type="String"> 你好,世界! </assert> </asserts> </test> <test id="jp"> <objs> <obj id="locale" type="String">ja_JP</obj> </objs> <asserts> <assert id="result" action="isEqual" type="String"> こんにちは、世界! </assert> </asserts> </test> </group> </cluster> </ddtunit>
- 新建类 DDTUnitTestBase 并继承 DDTTestCase 类,实现其 initContext() 方法如下:
清单 2. 初始化
public void initContext() { initTestData("/DDT-TestResource.xml", "MyClassTest"); }
- 新建被测试类 DemoTestCases,提供 sayHello() 方法,该方法可以根据输入 locale 不同输出不同的问候语。见清单 3:
清单 3. SayHello 方法
public String sayHello(String locale) { String resultStr = null; if(locale==null||"".equals(locale)||"en_US".equals(locale)) { resultStr = "HelloWorld!"; } else if("zh_CN".equals(locale)) { resultStr = "你好世界!"; } else if("ja_JP".equals(locale)){ resultStr = "こんにちは世界!"; } return resultStr; }
- 新建类 DDTUnitTest 提供默认构造方法,并添加
testHelloWorld
() 方法如下:
清单 4. 第一个 TestCase
public void testHelloWorld () { String locale = (String) getObject("locale"); String result = sayHello(locale); assertObject("result", result); }
- 控制台输出
INFO: [class com.ddtunit.test. DemoTestCases] (1/3) method "testHelloWorld", test "en" Aug 26, 2010 6:31:32 PM junitx.ddtunit.DDTRunMonitor endMethodTest INFO: [class com.ddtunit.test. DemoTestCases] (2/3) method "testHelloWorld", test "zh" Aug 26, 2010 6:31:32 PM junitx.ddtunit.DDTRunMonitor endMethodTest INFO: [class com.ddtunit.test. DemoTestCases] (3/3) method "testHelloWorld", test "jp" Aug 26, 2010 6:31:32 PM junitx.ddtunit.DDTRunMonitor endTest INFO: [class com.ddtunit.test.DemoTestCases] method "testHelloWorld": 3 of 3 test(s), 0 error(s), 0 failure(s)
- 我们的项目视图
图 2. DDTUnitTest 工程
上述测试用例对 DemoTestCases 类的 sayHellWorld() 方法进行了测试。从这个例子我们可以体验到使用 DDTUnit 能给我们带来的好处。
对于 sayHelloWorld() 方法,试想如果简单的仅使用传统的 JUnit TestCase 进行测试,我们需要编写至少三个测试用例来测试:
- public void testEnUSLocale();// 对应 locale = en _US 的情形
- public void testZhCNLocale();// 对应 locale = zh_CN 的情形
- public void testJaJPLocale();// 对应 locale = ja_JP 的情形
另外,在例子中只是测试英文、中文、日文的情况,在复杂的情况,如果我们要测试的 locale 更多的情况呢?比如德文、法文、阿拉伯文等。那么我们要相应的编写新的测试用例、调试、运行等,这无疑是一个巨大的工作量。
而在使用 DDTUnit 后,无论我们需要测试多少种可能的 locale, 我们只需要上述一个测试用例,即使在需要新增测试用例时,我们所需要做的只是维护 DDT-TestResource.xml 文件,而不用重新编写新的测试用例。更为方便的是,DDTUnit 使我们在修改 XML 数据文件后,不需要重新编译 Java 文件,而可以直接运行新增测试用例。
DDTUnit 不仅支持简单数据类型的作为测试数据,而且支持自定义复杂自定义数据对象以及对诸如 Collection,Map 等集合类的判定。
在下面的例子中我们自定义 Money 类,该类有 yuan、jiao、fen 三个属性,我们将测试该类实例相加的情况。请参见下面的代码:
清单 5. Money 类定义
package com.ddtunit.test.bean; public class Money { public int yuan; public int jiao; public int fen; ... ... public Money add(Money mon) { int yu; int ji; int fe; yu = this.yuan + mon.yuan; ji = this.jiao + mon.jiao; fe = this.fen + mon.fen; return new Money(yu,ji,fe); } public boolean equals(Object obj) { if((obj==null)||!(obj instanceof Money)) { return false; } Money mon = (Money)obj; if((this.yuan == mon.yuan)&&(this.jiao == mon.jiao)&&(this.fen == mon.fen)) { return true; } else{ return false; } } public int hashcode() { return this.yuan*100+this.jiao*10+this.fen; } } |
清单 6. 数据定义文件
<group id="testMoney"> <test id="positiveAdd"> <objs> <obj id="number1" type="com.ddtunit.test.bean.Money"> <yuan>4</yuan> <jiao>5</jiao> <fen>6</fen> </obj> <obj id="number2" type="com.ddtunit.test.bean.Money"> <yuan>1</yuan> <jiao>2</jiao> <fen>3</fen> </obj> </objs> <asserts> <assert id="result" action="isEqual" type="com.ddtunit.test.bean.Money"> <yuan>5</yuan> <jiao>7</jiao> <fen>9</fen> </assert> </asserts> </test> <test id="negativeAdd"> <objs> <obj id="number1" type="com.ddtunit.test.bean.Money"> <yuan>4</yuan> <jiao>5</jiao> <fen>6</fen> </obj> <obj id="number2" type="com.ddtunit.test.bean.Money"> <yuan>-1</yuan> <jiao>-2</jiao> <fen>-3</fen> </obj> </objs> <asserts> <assert id="result" action="isEqual" type="com.ddtunit.test.bean.Money"> <yuan>3</yuan> <jiao>3</jiao> <fen>3</fen> </assert> </asserts> </test> </group> |
清单 7. 测试用例
public void testMoney() { Money mon1 = (Money)getObject("number1"); Money mon2 = (Money)getObject("number2"); Money result = mon1.add(mon2); addObjectToAssert("result",result); } |
上例采用用户自定义 Money 类作为测试数据类型,详细说明了 DDTUnit 对复杂对象作为数据类型情况下的支持与使用方法。通过支持用户自定义数据类型,在很大程度上扩展了 DDTUnit 的应用广度与深度。
DDTUnit 框架功能的强大很大程度在于它提供了对用户自定义数据类型的支持。用户通过自定义的数据类型,可以进行更广泛意义上的数据内容的获取并对其进行断言。
DDTUnit 支持下列自定义数据类型方式:
- 通过默认构造函数和属性
- 通过默认构造函数和 Bean
- 通过默认构造函数与方法调用
- 定义常量对象
- 定义数组类型
- 定义容器类型,包括 Collection 和 Map
- 定义断言对象
下面我们分别介绍这几种自定义数据类型的方式。
清单 8. XML 定义 Schema
<objs> <obj id="myId" type="my.class.Type" int="fields"> ... </obj> <objs> |
通过类构造函数和属性声明对象的前提是要使用确切的对象的属性名,DDTUnit 以此通过 Java 的反射机制来调用类构造函数并对其属性赋的相应的值。
清单 9. XML 示例
<test id="myFirstTestCase"> <objs> <obj id="myObj" type="junitx.ddtunit.resources.SimpleVO"> <doubleValue>12.4</doubleValue> <integerValue>4711</integerValue> <stringValue>My Text</stringValue> </obj> </objs> </test> |
清单 10. 自定义类实现
public class SimpleVO { private Integer integerValue; private String stringValue; private Double doubleValue; /** * Default constructor. */ public SimpleVO() { // no special initialization neccessary } } |
清单 11. XML 定义 Schema
<objs> <obj id="myId" type="my.class.Type" hint="bean"> ... </obj> <objs> |
通过类构造函数和 Bean 声明对象的前提是要使用确切的对象的 setter 方法名以及输入参数类型,DDTUnit 以此通过 Java 的反射机制来调用类构造函数并对其属性赋的相应的值。
清单 12. XML 示例
<test id="myFirstTestCase"> <objs> <obj id="myObj" type="junitx.ddtunit.resources.SimpleVO"> <doubleValue>12.4</doubleValue> <integerValue>4711</integerValue> </obj> </objs> </test> |
清单 13. 自定义类实现
public class SimpleVO { private Integer integerValue; private String stringValue; private Double doubleValue; /** * Default constructor. */ public SimpleVO() { // no special initialization neccessary } public void setDoubleValue(Double arg){ // whatever check or calculation you like to process ... this.doubleValue = arg; } public void setIntegerValue(Integer arg){ // whatever check or calculation you like to process ... this.integerValue = arg; } } |
清单 14. XML 定义 Schema
<objs> <obj id="myId" type="my.class.Type" hint="call" calltype="my.call.Type" method="" > ... </obj> <objs> |
通过类构造函数和方法调用声明对象 , 用户可以指定构造函数甚至是某一静态工厂类的方法。 DDTUnit 通过用户指定方法利用 Java 的反射机制来调用类构造函数并对其属性赋的相应的值。
清单 15. XML 示例
<obj id="myObj" type="junitx.ddtunit.resources.SimpleVO" calltype="junitx.ddtunit.resources.SimpleVO" hint="call" method="constructor"> <item type="string">My Text</item> <item type="int">4711</item> <item type="double">12.4</item> </obj> |
其中:
hint=call: 表示使用构造函数或者指定方法创建对象。
method=methodName: 指定调用方法名,调用方法为构造函数时可以设定 method=constructor。如果调用方法没有输入参数时,可以指定 method=toString。
Calltype=my.call.Type:指定调用方法所属类。
清单 16. 自定义类实现
public class SimpleVO { ... public SimpleVO(String text, Integer intValue, Double doubValue) { // whatever your class should do on instanciation } ... } |
清单 17. XML 定义 Schema
<objs> <obj id="myId" type="my.class.Type" hint="constant"> ... </obj> <objs> |
需要说明的一点是,type 指定的常量所属类型,这个常量必须是类静态常量。
清单 18. XML 示例:
<test id="myFirstTestCase"> <objs> <obj id="myObj" type="junitx.ddtunit.resources.SimpleConstants" hint="constant"> MY_STRING_CONSTANT </obj> </objs> </test> |
清单 19. 自定义类实现
public class SimpleConstants { public final static String MY_STRING_CONSTANT = "Hallo World"; /** * Default constructor. */ private SimpleConstants() { // no special initialization neccessary } |
清单 20. XML 定义 Schema
<objs> <obj id="myId" type="my.class.Type" hint="array" > ... </obj> <objs> |
清单 21. XML 示例
<test id="myFirstTestCase"> <objs> <obj id="myObj" type="int" hint="array"> <item>4711</item> <item>4712</item> </obj> </objs> </test>` |
清单 22. 自定义类实现
Integer [] myObj = { new Integer(4711), new Integer(4712)}; |
清单 23. XML 定义 Schema
Collection: <objs><obj id="myId" type="my.class.Type" hint="collection">...</obj><objs> Map: <objs><obj id="myId" type="my.class.Type" hint="map">...</obj><objs> |
清单 24. XML 示例:
Collection: <obj id="myVector" type="java.util.Vector" hint="collection"> <item type="string">firstEntry</item> <item type="string">secondEntry</item> <item type="string">thirdEntry</item> </obj> Map: <obj id="myMap" type="java.util.HashMap" hint="map"> <item> <key type="java.lang.String">firstkey</key> <value type="java.lang.String">firstValue</value> </item> <item> <key type="java.lang.String">secondkey</key> <value type="java.lang.String">secondValue</value> </item> </obj> |
清单 25. 自定义类实现
Collection: Vector vec = new Vector(); vec.put("firstEntry"); vec.put("secondEntry"); vec.put("secondEntry"); Map: Map map = new Hashmap(); map.put(firstkey, firstValue); map.put(secondkey, secondValue); |
清单 26. XML 定义 Schema
<asserts> <assert id="myId" type="my.class.Type" hint="myParseHint" action="MYACTION"> ... </assert> <asserts> |
DDTUnit 支持非常灵活的断言方法,其中 action 可选的值包括下面的字符串:
表 1. action 属性可选值
名称 | 描述 |
---|---|
isEqual | 对应 JUnit Assert.assertEquals(). |
isNotEqual | 对应 JUnit-Addons Assert.assertNotEquals(). |
isSame | 对应 JUnit Assert.assertSame(). |
isNotSame | 对应 JUnit-Addons Assert.assertNotSame(). |
isNull | 对应 JUnit Assert.assertNull(). |
isNotNull | 对应 JUnit-Addons Assert.assertNotNull(). |
isTrue | 对应 JUnit Assert.assertTrue(). |
isFalse | 对应 JUnit-Addons Assert.assertFalse(). |
isContainedIn | 对应 JUnit-Addons Assert.assertTrue(((Collection)obj).contains(actual)). |
isNotContainedIn | 对应 JUnit-Addons Assert.assertFalse(((Collection)obj).contains(actual)). |
isLT | 实际小于预期 |
isNotLT | 实际不小于预期 |
isGT | 实际大于预期 |
isNotGT | 实际不大于预期 |
isInRange | 实际在预期范围内 |
isNotInRange | 实际不在预期范围内 |
清单 27. XML 示例
<assert id="complete" action="isEqual" type="junitx.ddtunit.resources.SimpleVO"> <doubleValue>12.4</doubleValue> <integerValue>4711</integerValue> <stringValue>My Text</stringValue> </assert> |
所有以前面方式声明的类型都可以在 Assert 断言中定义。有一点需要注意的是必须覆盖类的 equals() 和 hashCode() 方法,从而 DDTUnit 可以通过这两个方法进行对象的比较。
清单 28. Java 实现
public void testMyService(){ SimpleVO simpleVO = MyServiceUnderTest.getSimpleVO(); addObjectToAssert("complete", simpleVO); } |
其中方法 public addObjectToAssert(String id, Object toAssertAgainst),提供了断言的方式。