Jtester 使用详细介绍


一步一步学写测试 吴大瑞 2009.12.25 Jtester 的项目配置 1、 martini 下项目配置 2、 使用 ant 的项目配置 3、 Maven 项目的配置 5、jtester.properties 文件配置 TestNG 测试 一个简单到不能再简单的测试 您只要用标注 @Test 通知框架这个类的方法是测试。 @Test 这个标注可以写在测试类 class 前,也可以写在测试方法 method 前。 写在测试类前,表明这个类中签名为 public void 的方法都是一个测试方法。 清单 1 演示了实用类 StringUtils 的一个最简单的测试。它测试 StringUtils 的两个方法: isEmpty() 方法检测 String 是否为空; trim() 方法从 String 两端 删除控制字符。请注意,其中使用了 Java 指令 assert 来检测错误情况。 import com.beust.testng.annotations.*; import org.apache.commons.lang.StringUtils; public class StringUtilsTest{ @Test public void isEmpty() { assert StringUtils.isBlank(null); assert StringUtils.isBlank(""); } @Test public void trim() { assert "foo".equals(StringUtils.trim(" foo ")); } } @Test 表明这 2 个方法是测试方法 在 eclipse 中运行 testng 测试 安装eclipse插件:http://beust.com/eclipse/ ,安装好TestNG后, 在Eclipse 中单击"Window"->Show View->Other->Java->TestNG, TestNG的视图就打开了。 在测试类的 java editor 中右键菜单中会出现如下选项 运行 testNG Test 就可以跑这个类中的所有测试方法了。 如果你要跑指定的测试方法,可以打开 outline 视图 右键选定你要跑的测试,运行 testng test 选项,就可以跑你指定的测试方 法,而不用跑这个类了。 定义测试组 TestNG 可以将一个测试方法定义为属于一个或多个测试组,但可以选择只 运行某个测试组。要把测试加入测试组,只要把组指定为 @Test 标注的参数, 使用的语法如下: @Test(groups = {"tests.string"}) 如果这个 annotation 是加在 class 前面,表明这个类的所有测试方法都属于这 个测试组,如果这个 annotation 是加在 method 前,只表明这个方法属于这个测 试组。同时,测试组是可以继承和叠加的。 在上面的例子中,方法 test1 属于测试组 mytest1,方法 test2 属于测试组 mytest1 和 mytest2,方法 test3 属于测试组 mytest1、mytest2 和 mytest3。 运行指定的测试组,在 eclipse 的 run 菜单下单击”Run Configuration”选项,如下图: 则会打开如下界面 @Test(groups={"mytest1"}) public class BaseTest{ public void test1(){…} } @Test(groups={"mytest2"}) public class ConcreteTest extends BaseTest{ public void test2(){…} @Test(groups={"mytest3"} public void test3(){…} } Name 这个选项是你给当前的测试一个命名,你可以随便取,project 选项是你要跑的测 试位于那个项目下。Class、method、groups、package 和 suite 这 5 个选项表明你可以按这 5 种方式来运行的测试。现在,我们选中 groups 选项,然后点击这个选项后对应的 Browse 按 钮,会弹出一个窗口,列出所有可见的测试组,你可以选择你希望跑的测试组,然后点击 OK 和 RUN 按钮,eclipse 就会自动运行所有属于选中的测试组的测试方法。 运行结果会显示在 TestNG view 视图中,红色表示这些测试没有通过,相应的异常会显 示在 Failure Exception 中。 如果测试方法运行是成功的,则 TestNG view 视图会显示绿色进度条。 生命周期 使用 TestNG,不 仅可以指定测试方法,还可以用专门的 Annotation 指定类 中的其他特定生命周期: @BeforeSuite, 在测试套件之前运行 @AfterSuite, 在测试套件之后运行 @BeforeTest, 在测试方法运行之前运行 @AfterTest,在测试方法之后运行 @BeforeGroups, 在测试组之前运行 @AfterGroups, 在测试组之后运行 @BeforeClass, 在测试类之前运行 @AfterClass, 在测试类之后运行 @BeforeMethod,在测试方法之前运行 @AfterMethod,在测试方法之后运行 一个可能的生命周期示例图如下: 在 testng 中使用 jtester 在 testng 中使用 jtester 非常简单,只需用你的测试类继承 org.jtester.testng.JTester 就可以了。 断言的使用 在 jtester 中实现了强大的 fluent interface 式的断言语法。其语法格式如下: want.object(value).assert()... want 后面跟着你要断言的对象类型,目前 jtester 提供了下列几种对象的断言 o object o string o number (int,double,float,long) o boolean o collection (list,set,collection) o date/calendar o array (数组) o map o file 对象后面紧跟着具体的对这个对象属性的判断 所有对象都支持的断言 命令 说明 isEqualTo(T expected) 对象等于期望对象 示例 want.string("test").isEqualTo("test") notEqualTo(T expected) 对象不等于期望的值 示例 want.string("test1").isEqualTo("test2") in(T... values) 对象可以在期望值里面找到 示例 want.number(2).in(1,2,3) notIn(T... values) 对象不在指定的值里面 示例 want.character('a').notIn('b','c'); clazIs(Class claz) 对象的 class 等于期望的 class 对象实例 示例 want.map(new HashMap()).clazIs(String.class); is(Matcher matcher) 对象符合指定的断言器所指定的行为 示例 want.string("1234").is(the.string().contains("23")); not(Matcher matcher) 对象不符合断言器指定的行为 示例 want.string("1234").not(the.string().contains("13")); allOf(IAssert matcher1, IAssert matcher2, IAssert... matchers) 对象满足参数里指定的所有断言 allOf(Iterable matchers) 同上 anyOf(IAssert matcher1, IAssert matcher2, IAssert... matchers) 对象符合任意一个断言就可以 anyOf(Iterable matchers) 同上 same(T value) 对象和被期望值是同一个对象 any() 对象可以是任意值 isNull() 对象值等于 null notNull() 对象只不为 null reflectionEq(Object expected, ReflectionComparatorMode.. . modes) 断言对象和期望对象在形式上是相等的 示例 want.object(Arrays.asList(3, 2, 1)).reflectionEq(Arrays.asList(1, 2, 3)) lenientEq(Object expected) 断言对象和期望对象在某种形式上是相同的,但 忽略对象顺序关系 示例 want.object(Arrays.asList(3, 2, 1)).lenientEq(Arrays.asList(1, 2, 3)) propertyEq(String property, Object expected) 对象指定的属性(property)值等于期望值 示例 want.object(employee).propertyEq("name", "my name"); propertyMatch(String property, Matcher matcher) 对象指定的属性符合 matcher 定义的行为 示例 want.object(user).propertyMatch("name", the.string().contains("john")) number 和 string 对象支持的断言 命令 说明 lessThan(T max) 对象小于期望值 max 示例 want.number(3).lessThan(4); lessEqual(T max); 对象小于等于期望值 max w 示例 ant.number(3).lessThan(3); greaterThan(T min) 对象大于期望值 min 示例 want.string("abc").greaterEqual("aaa"); greaterEqual(T min) 对象大于等于期望值 min 示例 want.string("abc").greaterEqual("aaa"); between(T min, T max) 对象在最小值和最大值之间(包括最大值和最小值) 示例 want.number(2.3).between(2d, 3d); array 和 collection 对象支持的断言 命令 说明 sizeIs(int size) 数组长度或 collection 中元素个数等于期望值 sizeEq(int size) 数组长度或 collection 中元素个数等于期望值 sizeGt(int size) 数组长度或 collection 中元素个数大于期望值 sizeGe(int size) 数组长度或 collection 中元素个数大于等于期 望值 sizeLt(int size) 数组长度或 collection 中元素个数小于期望值 sizeLe(int size) 数组长度或 collection 中元素个数小于等于期 望值 sizeNe(int size) 数组长度或 collection 中元素个数不等于期望 值 hasItems(Collection coll) 数组或集合中的元素包含期望集合中列出的元 素 hasItems(Object value, Object... values) 数组或集合中的元素包含期望元素 hasItems(Object[] values) 数组或集合中的元素包含期望数组中列出的元 素 hasItemMatch(String regular, String... regulars) 参数中列出的正则表达式可以被数组或集合中 的元素满足 allItemMatch(String regular, String... regulars) 数组或集合中所有的元素 toString()必须满足 所有列出期望的正则表达式 string 对象支持的断言 命令 说明 contains(String expected) 字符串包含期望的字串 expected 示例 want.string("ddd").contains("d").contains("de"); end(String expected) 字符串以 expected 子串结尾 示例 want.string("eeeed").end("ed"); start(String expected) 字符串以 expected 子串开头 示例 want.string("eeeed").start("eee"); regular(String regex) 字符串符合正则表达式 regex 示例 eqIgnoreCase(String string) 字符串在忽略大小写的情况下等于期望值 示例 want.string("abC").eqIgnoreCase("aBc"); eqIgnorBlank(String string) 字符串在忽略前后空格的情况下等于期望值 示例 want.string(" ab ").eqIgnorBlank("ab"); notContain(String str) 字符串不包含特定的子串 notBlank() 字符串不是空白串 map 对象支持的断言 命令 说明 hasKeys(Object key, Object... keys) map 包含参数中列出的 key 值 示例 want.map(maps).hasKeys("one", "two"); hasValues(Object value, Object... values) map 包含参数中列出的值对象 示例 want.map(maps).hasValues("my first value", "my third value"); data/calendar 对象支持的断言 命令 说明 yearIs(int year) 日历值的年等于期望值 yearIs(String year) 日历值的年等于期望值 monthIs(int month) 日历值的月份等于期望值 monthIs(String month) 日历值的月份等于期望值 dayIs(int day) 日历值的日期等于期望值 dayIs(String day) 日历值的日期等于期望值 hourIs(int hour) 日历值的小时(24 小时制)等于期望值 hourIs(String hour) 日历值的小时(24 小时制)等于期望值 file 对象支持的断言 命令 说明 isExists() 指定的文件存在 unExists() 指定的文件不存在 在上文中那个简单的 StringUtil 测试采用的是 java 自带的 assert 语法,改为 jtester 断言 语法实现如下: Spring 容器的加载 Jtester 框架对 spring 的使用,没有做更多的扩展,基本上就是 unitils 提供的功能。 spring 容器的启动 加载 spring 上下文的定义,可以使用@SpringApplicationContext 轻易的搞定。 就这样,jtester 框架会创建一个 ApplicationContext,并且会到 classpath 下面去寻找 config1.xml 和 config2.xml 这 2 个文件,并把它们加载进来。Jtester 会先扫描测试类的 superclass,如果 superclass 定义了@SpringApplicationContext,那么这个子类同样也会使用 superclass 定义好的 applicationcontext,如果子类也定义了@SpringApplicationContext,那将 使用子类定义的。 如果 spring 文件是在 classpath 下,只需要指定 spring 的 classpath 路径就可以了。 假定我们的 spring 文件的 classpath 如下图所示 import com.beust.testng.annotations.*; import org.apache.commons.lang.StringUtils; public class StringUtilsTest{ @Test public void isEmpty() { want.bool(StringUtils.isBlank(null)).is(true); want.bool(StringUtils.isBlank("")).is(true); } @Test public void trim() { String str = StringUtils.trim(" foo "); want.string(str).isEqualTo("foo"); } } @SpringApplicationContext({"config1.xml", "config2.xml"}) public class UserServiceTest extends JTester { } 我们就只需要 classpath 的路径 + spring 文件名,就可以了。当然你也可以在前面加上前 缀"classpath:",但这个不是必须的。 你也可以使用文件路径的方式来查找 Spring 配置文件,格式如下 “file:文件路径” 如果你的 spring 文件是定义在兄弟工程中,就需要使用 "file:../兄弟工程目录/spring 文 件相对于项目跟目录的路径/spring 文件名称.xml" 这样指定。 注意:这里的兄弟工程目录指的是 disk 上的路径,不是 eclipse 中的 project 名称 使用文件路径的时候,请使用相对路径的方式,不要使用绝对路径,因为测试 程序不是你一个人会执行,其他人会 checkout 下来执行,服务器端也可能会定时执行。 spring bean 的使用 Jtester 初始化好 springapplicationcontext 好后,我们就可以在测试类中使用 spring bean 了。可以使用下列 3 个 annotation 将 spring bean 注入到测试类中 @SpringBean("userService") 显式的指定要注入那个 spring bean @SpringBeanByType 按类型方式注入 @SpringBeanByName 按名称方式注入 @SpringApplicationContext({"spring/project-service.xml", "spring/project-placeholder.xml", "classpath:spring/project-datasource.xml"}) public class SpringLoadExampleTest extends JTester{ //…… } @SpringApplicationContext({"file:./src/test/resources/spring/proje ct-service.xml", "file:./src/test/resources/spring/project-placeholder.xml", "file:./src/test/resources/spring/project-datasource.xml"}) public class SpringLoadExampleTest extends JTester{ //….. } 使用 annotation 方式注入 spring bean,并不需要在测试类中声明对应的 set 方法。 FAQ 如何测试一个实现类中的 private 或 protected 方法。 场景 ,用户定义了一个接口和实现,如下图 其中,在实现中用户还定义了一个内部方法 internalCall,现在用户想针对 internalCall 进行 测试,但又不想把 internalCall 暴露到接口中,怎么办? 我们可以在测试类中临时定义一个扩展接口和实现,把 internalCall 方法暴露出来。 @SpringBean("userService") private UserService userService; @SpringBeanByName private UserService userService; @SpringBeanByType private UserService userService; 同时 spring 的定义如下: JMock 使用 使用 mock 的场景 真实对象有着不确定的行为 真实对象很难创建 真实对象的行为很难触发 真实对象响应缓慢 真实对象是用户界面 真实对象使用了回调机制 真实对象尚未存在 而对应的 mock 具有下面的功能 替换远程对象,如 ESB、WEB Service 对象等 替换复杂的对象 方便模块化开发 … … jmock 的使用 在 JTester 中使用 jmock 很简单,只需要你在要 mock 的接口和类的变量前加个@Mock 一个简单的示例 someInterface1 和 someInterface2 是你要 Mock 的 2 个接口变量,同时要将这 2 个变量注 入到变量 springBeanService 中。 @Mock 表示这 2 个变量时 mock 变量,具体在下面程序中就可以指定 mock 的行为。 Mock Annotation 有 2 个属性: Mock annotation 属性 作用 injectInto 表示这个 mock 对象要注入到对应的那个对象中,jtester 会在测试 方法中自动查找字段名称一致的变量。比如上面的例子中 injectInto = "springBeanService" , Jtester 就会在 MockTest_ByName 这个测试类中查找名称为"springBeanService"的 变量。 injectInto 可以跟多个字段名称,比如 injectInto = {"springBeanService1","springBeanService2"},就可以 讲 mock 对象注入到这 2 个变量中。 byProperty 表示 mock 对象要注入到被测对象(injectInto 所指定的对象)的那 个属性上。在上面的例子中表示 someInterface1 这个 mock 对象要 注入到 springBeanService 的 dependency1 这个属性上。 如果 byProperty 这个属性为 null 或者=””,那么表示 mock 对象是按 类型方式注入到被测对象中,注意:如果按类型方式注入,则被测 对象中不能有多于一个 mock 类型的属性。 @Test(groups = { "jtester", "mock-demo" }) public class MockTest_ByName extends JTester { private SpringBeanService springBeanService = new SpringBeanServiceImpl1(); @Mock(injectInto = "springBeanService", byProperty = "dependency1") private SomeInterface someInterface1; @Mock(injectInto = "springBeanService", byProperty = "dependency2") private SomeInterface someInterface2; @Test public void testMock_ByName() { want.object(springBeanService.getDependency1()).same(someInterface1); want.object(springBeanService.getDependency2()).same(someInterface2); } } byProperty 同样也支持多个属性名称,分别对应 injectInto 中指定的 被测对象。 mock spring bean 大多数的应用程序都用到了 spring,那怎么使用 mock 对象替换 spring 文件中定义的具 体的 bean 对象呢。Jtester 框架提供的@MockBean 这个 annotation 可以让你很轻松的做到这 一点。 假设我们有下面这个 spring 配置文件 userService 依赖 userDao 这个 bean,那么在 spring 容器起来后,userDao 会自动的被注 入到 userService 这个 bean 中。如果我们要使用 mock 对象替换 userDao,那么我们可以使用 @MockBean 功能。 很轻松吧,就这样,一个 mock 的 userDao 就替换了 spring 文件中定义的具体的 spring bean。而 spring 容器初始化完成后,userService 中的依赖对象就是一个 mock 的 userDao 了。 使用@MockBean,你甚至不需要在 spring 文件中定义 userDao 这个具体的 bean,mockbean 会自动的帮你往容器中注入一个 Mock bean 对象。这个特性在测试中会减少你配置 spring 文件的工作量,你只需要关心你真正要测试的对象,而不需要关心任何其他的你不想关心的, 想要被 mock 掉的对象。 @Test(groups = { "jtester", "mock-demo" }) @SpringApplicationContext( { "org/jtester/fortest/spring/beans.xml ", "org/jtester/fortest/spring/data-source.xml" }) public class MockBeanTest extends JTester { @SpringBeanByName private UserService userService; @MockBean private UserDao userDao; //… } 使用@Mock 和@MockBean 的区别 使用@Mock 也可以将 mock 对象注入到 spring bean 中,示例如下 这样也可以将 userDao 这个 mock 对象注入到 userService 这个 spring bean 中,但 并不是替换 spring 容器中的 userDao 这个 bean,如果 spring 容器中还有另外一个 bean userHandler 也依赖于 userDao,那么这个 userHandler 所依赖的 userDao 将是 spring 加载的真 实的 userDao 这个 bean,而不是 mock 对象。 @MockBean 是彻底将 spring 容器中对应的 bean 对象给 mock 掉,不管是 userService 还 是 userHandler 所依赖的 userDao,取出来的都是 mock 对象。 mock 对象行为的指定 Jmock 的期望行为是定义在一个匿名类里面的,其语法格式如下所示 Mock 对象的期望一般分为 3 种: A、 期望发生的动作(api) B、 动作(api)期望接收的参数 C、 动作(api)发生后,期望返回的值(或抛出异常) A、 mock 对象期望发生的动作。其语法形式如下所示: will.call.one(mock object).calledMethod(para1,para2); 这条语句表示期望调用 mock object 对象的方法 calledMethod 一次,且参数为 para1,para2。期望调用的次数如下图所示: @SpringBeanByName private UserService userService; @Mock(injectInto = "userService", byProperty = "userDao") private UserDao userDao; Mock 对象 匿名类,用于指定你期望 mock 对象发生的行为 具体说明 命令 说明 will.call.oneOf(mock).method(...) 指定的动作发生且只发生一次 will.call.allowing(mock).method(...) 指定的动作可以发生任意次,包括不发生 will.call.ignoring(mock).method(...) 同 allowing,只是使用的语境不一样,一个表 明允许,一个表示忽略。 will.call.exactly(3).of(mock).method(...) 指定的动作发生且发生的次数等于指定的次 数,例子中表示发生 3 次。 one(mock).method(...)是 exactly 的特例。 will.call.atLeast(3).of(mock).method(...) 指定的动作至少发生指定的次数 will.call.atMost(2).of(mock).method(...) 指定的动作至多发生指定的次数 will.call.between(0,3).of(mock).method(...) 指定的动作发生 0 到 3 次 will.call.never(mock).method(...) 指定的动作永远不能被调用 B、动作(方法)期望接收到的参数 方法参数的指定和断言的语法是一样的,其形式如下:the.string.assert(...).want() want()的作用是把断言器转换为 api 所需要的参数类型,结束断言。 如果你的 api 参数的类型不在断言类型中,比如你的参数类型是 HashMap,而断言类型 中只要 map 类型,那么你作为断言以后需要做一个类型转换。 the.map()…..wanted(HashMap.class) 具体的语法请参照断言部分。 C、动作(方法)发生后期望返回的值(或异常) 在指定发生的动作后面,我们可以紧跟一条语句用于指明动作发生的返回值 返回值 说明 will.returns(value) 表示返回指定的值 will.throwException(throwable) 表示抛出指定的异常 一个完整的例子 使用录制的 mock 对象 下载插件,安装。 http://core-sys-dept.alibaba-inc.com:9999/job.php?action=download&pid=tpc&tid=72&aid=23 在 eclipse 的 debug watch expression view 中的右键菜单中会多出一项"save expression" @Mock @InjectIntoByType private CalledService calledService; @TestedObject private CallingService callingService = new CallingService(); @Test public void register_SinglThread() { checking(new Je() { { will.call.one(calledService).called(the.string().contains("te st").wanted()); will.returns.value("dddd"); will.call.ignoring(calledService).called(the.string().any().w anted()); will.returns.value("dddd"); } }); callingService.call("i am a test message!"); } 点击"Save Expression"就可以将变量的中保存到项目的 jave.test 相应的 package 下,文件的名称是就是变量的名称,如果同名的文件已经存在,那么插件会自动给新保存的文件 名称加上一个时间。 录制对象的使用 1、使用 api 方式反序列化录制对象 对象反序列化回来后,可以在任意地方使用。 2、直接在 mock 对象中使用 如果你期望一个 mock 对象返回预先录制好的对象,使用方法如下: 各个参数的含义同上。 RecordedObject co = SerializeUtil .fromXML(RecordedObject.class, FileLacator.class, "recorded.xml"); 录制的对象类型 文件保存的 package 下的任 一 class,用于查找文件方便 录制文件名称 will.returns.value(RecordedObject.class, FileLacator.class, "recorded.xml"); Mock FAQ 问题:测试结果抛出下列异常表示什么意思 unexpected invocation: lightMailSendAdapter#lightMailSendAdapter#1.send("-1" , <2>, <[{KEY_SUBJECT=test mail, KEY_TO_A 是指在 mock 的 checking(new Je())中你指定了要调用到这个接口,但实际程序中你根本就 没有用到这个接口。 解决:如果你不确定这个方法会被调到,或者不确定调用的次数,你可以使用宽松一点的行 为指定,比如 allowing()。 问题:为什么我的 mock 对象是 null 解决: A、你的测试类是否继承了 JTester 基类。 B、你的@Test 是基类 JTester 是否 package 一致,jtester 同时支持 testng 和 junit,会不会你 的 JTester 是 testng 的,但@Test 是 junit 的? C、你的 mock 对象没有注入到测试对象中,或者注入后又被改写了。 问题:Mock 的方法有异常抛出怎么办 解决:如果你不是测试异常,那么你可以直接向 test 方法抛出异常。 如果你要测试异常可以在@Test 中指明异常的类型 如果你不仅仅要判断测试的类型,而且还要判断异常里面具体的消息,那么你就需要在测试 代码里面 try{…}catch(){…}进行判断了。 问题:我如何改变传给 mock api 的参数的值 解决:可以在 mock 方法后面加 will(new Action(){…})来改变参数的值。 如上图所示,我想改变参数 list 的值,那么在 invoke 方法中把第一个参数取出来,进行操作 进可以了。需要注意的是 invoke 方法方法的是模拟的方法返回值(即 intf.addElement 的返 回值),这样你要把原来在 will.returns.value()中返回的值或抛出的异常,放到这里来操作。 如果你的方法是 void 的,那么使用 return null; 数据库测试 jtester.properties 文件配置 jtester.properties 是 jtester 的运行配置文件,是放在 classpath 的根目录下,要使用 jtester 的数据库测试功能,必须在 jtester.properties 文件中指定下列配置项。 配置项 说明 database.type 数据库测试类型 database.driverClassName 数据库驱动的 classname database.url 数据库连接 url database.userName 用户名 database.password 密码 database.schemaNames Schema 名称,如果有多个 schema 用“;”分 割 DatabaseModule.Transactional. value.default=commit 事务的默认管理方式,默认是提交(commit), 可选值还有回滚(rollback),失效 (disabled) database.onlytest=true 是否只能连接本地数据库和以test开头或结 尾的数据库 spring.datasource.name=dataSource 使用jtester中的数据库配置替换spring中 id=${spring.datasource.name}的 datasource bean的数据库配置 jtester默认spring中dataSource bean 的配置为 … … dbMaintainer.disableConstraints.e nabled=true 是否解除数据库的外键约束和not null约束 示例 database.type=mysql database.url=jdbc:mysql://localhost/testdb database.userName=root database.password=password database.schemaNames=presentationtdd database.driverClassName=com.mysql.jdbc.Driver database.onlytest=true,是否只能连接本地数据库或者以test开头或结尾的测试数 据库。 推荐单独使用测试数据库,而不要使用开发数据库作为测试库使用,因为测试要求数据 是可重现的,这样必然要求要对数据库有完成的控制权,特别是要经常清空数据进行重新准 备数据。如果测试库和开发库合一的话,必然要相互干扰,要么测试的时候把开发要用的数 据给清空了,要么是开发的数据影响到了测试程序的运行准确性。 dbMaintainer.disableConstraints.enabled=true,是否 解除数据库的约束条件 (外键和not null约束) 解除数据库的约束条件对于测试来说有很重要的作用,外键的解除,让我们删除和准备 数据更加便捷;not null约束的解除可以让我们聚焦于我们感兴趣的数据项,而不必关心跟 本测试无关的数据项。一句话,解除约束可以让我们测试更自由。 另外为了使大家使用dbFit插件更方便,可以在jtester.properties中添加一条配置用来指定 数据库连接的driver jar包路径。 database.driverJar=D:/alibaba/repository.project/headquarters/jdbc/pr oxy/headquarters-jdbc-proxy-1.1.jar;D:/alibaba/antx/repository/Jakart a/commons/logging/commons-logging-1.1.jar;D:/alibaba/antx/repository/ jdbc/oracle/ojdbc14.jar 使用@DbFit 功能来进行数据库测试 Annotation DbFit 有 2 个属性,when 和 then,它们的值都是 wiki 文件数组,示例如下 在 testcase 执行之前,jtester 框架会先执行 when 里面指定的内容,when 通常用于准备 测试所用的数据。在 testcase 执行之后,jtester 框架会执行 then 里面指定的内容,then 通常 用于验证单元测试执行后的数据状态。虽然 when 和 then 在功能上有上面的所说的区别,但 在语法上都是一样的。 @DbFit 的 wiki 语法 Dbfit 是使用 wiki 来准备数据库测试数据和验证数据的,你可以在 eclipse 中直接进行编 辑,其效果如下图所示: 为了友好一点,你可以使用可视化的查看工具查看,eclipse 插件下载地址: http://eclipsewiki.sourceforge.net/ 其可视化效果图如下: @DbFit(when = { "testcase.when.wiki" }, then = "testcase.then.wiki") public void testcase() { //…… } 正如上图所示,dbfit 必须将命令和数据放在 wiki 表格中,这样 jtester 才可以识别和执行 相应的命令。Wiki 的表格语法是以”|”作为行开头,且内容是也是以”|”分割的。 |cell1|cell2|cell3|定义了表格的一行数据,其第一列数据内容为“cell1”,第二列是“cell2”, 第三列是“cell3”。 数据库连接 Jtester 连接数据库非常的简单,只需用一条命令 这样 jtester 就会默认使用 jtester.properties 中的数据库配置去连接对应的数据库。当然, 如果你的测试程序中使用到多个数据库,你也可以在 connect 命令后指定要连接的数据库。 如果你使用的是非标准的属性值指定的方式进行连接,而要直接使用一条 connection url 进行连接的话,也可以这样使用。 注意:在 dbfit 的所有 wiki 文件中,在使用数据库功能前,都要指定 connect 命令。 查看执行结果 Jtester 在执行 dbfit 模块时,会在项目的目录下生成一个 dbfit-output 文件夹,用于存放 dbfit 执行的结果。其文件名称和 when 和 then 中指定的名称加上 package 包名称。这个文件 中会详细的列出 dbfit 的执行状态和异常信息)。 |Connect | SERVICE_NAME | USER_NAME | PASSWORD | DATABASE_NAME| |Connect | a single connection URL | 假如你的 wiki 文件中没有以 |connect|命令开头,那么结果文件中就会显示如下异常 如果你的测试正常通过,那么你的结果文件中会有很多绿色的信息。 清空表数据 在单元测试中,为了保证每个 testcase 的独立性,case 的数据必须不受其他因素的干扰, 保证初始环境的可重复性。这样就要求在准备数据前,要把相应表中的数据清空。dbfit 模 块提供了一条简单的命令来清空多张表数据。 就这样,jtester 会将 table1,table2,table3 这 3 中表中的所有数据都清空。多张表名称可以用“;” 或“,”进行分割。 查询数据(Query) Query 语法格式 第一行第一列是关键字 Query 第一行第二列是一条具体的可执行的 SQL 语句,他的任务是查询出你想要的数据。 第二行是你期望验证的数据列名称列表,(名称列表可以少于 select 中指定的列,其顺序 也和 select 中指定的无关)。 | clean table | table1;table2;table3 | 第三行及其后行是你期望 sql 查询出的 ResultSet 中要包含的具体数据,数据的顺序是无 关的。 结果信息显示,如果测试通过,wiki 文件中的期望数据会以绿色显示。 如果 wiki 中指定的期望数据多于查询出的数据,结果文件中会以红色显示多出的数据, 并以 missing 字样标注 如果 wiki 中缺少了查询出的数据,结果文件中同样以红色显示少掉的数据,并以 surplus 字样标注。 技巧:query 后面指定的 table 数据只能比较字符串是否相等,没有比较大小等其他功能, 如果你的测试有这类的比较,如果 count 小于某个数值等等,可以在 select 语句中做比较, 在验证数据中做 true/flase 等判断。 有序查询(ordered query) Query查询的数据比较是顺序无关的,如果你需要严格验证数据的顺序,可以使用ordered query 命令。其语法结构如下: 其结构和 query 命令式一致的,唯一的区别是 value 的行是有顺序关系的。 |ordered query | select * from … | |field1|field2|…| |value11|value12|…| |value21|value22|…| |……| 参数化查询 在 dbfit 模块中是可以设置参数的,其语法格式如下 第一列是关键字 set parameter,第二列是参数的名称,第三列是参数值。 参数的引用很简单,@parameter,放在要使用的地方就可以了。 插入数据( Insert) Insert 命令式一般是用来准备测试数据的,其语法结构如下所示 第一行第一列是关键字 Insert 第一行第二列是要插入数据的表 第二行是数据表中对应的字段名称,(列可以少于表中的字段,按需插入数据) 第三行及其后行是要插入的数据值。 执行 SQL 语句(Execute) Execute 可以执行任何的 SQL 语句,其语法结构: |execute |delete from table where ……| 或者 |execute| update table set ……. wherer …..| commit&rollback dbfit 模块默认情况下更改的数据是回滚的,如果要使更改的数据生效,必须的 wiki 文 件末尾显式的加上 commit 命令。 | set parameter | parameter name | parameter value | 其它命令 执行存储过程 Update 命令 NULL 值 事务管理 在 jtester 中,dbift 和 spring 可以运行在同一个事务中,默认情况下,jtester 的事务是 commit 方式的,存在下列情况可以改变 jtester 中事务的运行。 1、 在 dbfit 的 wiki 文件中有显式的 commit/rollback 命令。 2、 Spring 管理的 dao/service 中存在 commit/rollback 的情况。 3、 测试方法或测试类显式的改变了 transaction 的类型。 @Transactional(TransactionMode.COMMIT) 最后commit数据 @Transactional(TransactionMode.ROLLBACK) 最后rollback数据 @Transactional(TransactionMode.DISABLED) jtester框架不在使用spring的事务 管理,程序员必须显式的在dbfit文件中自己commit或rollback数据。但spring bean仍然 接受spring transaction的管理 4、 Jtester.propeires 修改默认值,可选值有(commit,rollback,disabled) @Test @Transactional(TransactionMode.ROLLBACK) @DbFit(when = "…", then = "…") public void testDemo() { //…… } DatabaseModule.Transactional.value.default=commit Martini 项目下的 ibatis 文件配置 要进行数据库测试必然涉及到 spring 文件和 ibatis 文件的配置,这些文件管理的需要遵循 以下一些原则。 1、 按需加载原则,测试的一个准则是速度要快,很多 spring 文件和 ibatis 文件的加载 是很耗费时间的,不要把一些你根本用不到的文件也加载进来。 2、 文件模块化原则,spring bean 的配置要尽可能按照功能模块的方式来划分,不要把 所有的 spring bean 不加分析的写到一个文件中。配置文件也是代码,也需要我们进 行重构,文件太大了就要拆分。 3、 不要重复原则,在测试中能复用生产的配置文件时,一定要复用。不要拷贝复制一 份,这样做会导致后期的维护成本很高。 下面我们具体解析一下如何在 martini 中配置 spring 和 ibatis 文件时做到遵循这些原则。 数据库测试 FAQ 问题:为什么 dbfit 文件中 2 条 execute 语句只执行了一条,而第二条没有被执行。 答案:DbFit 是基于表格来解析命令的,一条命令一个表格,如果你有多条 execute 语句, 那么必须在 execute 语句之间加空行。 问题:spring 文件中指定了多个事务时,运行测试抛出异常 org.unitils.core.UnitilsException 原因:当 spring 容器中存在多个 transaction 时,测试框架不知道应该用哪个事务来管理测试的事务, 这个情况下只能在 test 前面加上 annotation : Found more than one bean of type Platform TransactionManager in the spring ApplicationContext for this class @Transactional(TransactionMode.DISABLED),取消测试方法的事务管理。 Spring 和 SQL 跟踪 升级 jtester 版本到 0.8.8 以上,在 jtester .properties 中加 2 项配置 tracer.database=true tracer.springbean=true 这样测试在运行的过程中就会自动生成 spring 接口调用的序列图,以及记录 SQL 语句。 生成的信息记录在 target/tracer 目录对应 package 下面的 html 文件中。 @Tracer 当个别测试方法有自己的跟踪策略时,可以在测试方法前面加@Tracer,@Tracer 有 3 个属 性。@Tracer(spring = true, jdbc = true, info = TOJSON) 当不想拦截 spring 调用时可以设置 spring=false 不想记录 SQL 语句时,设置 jdbc = false 当参数对象转换为 json 对象有错误时,可以使用 info=INFO.TOSTRING 代替,这样参数记 录就使用 object.toString()函数了。 FAQ 为什么 spring 初始化时报下面类型无法转换的错误 这是因为你的 spring bean 不是基于接口编程 你注入的是一个实现而不是一个接口,你应该把左边的写法改成右边的写法。 这个问题涉及的 spring 实现 AOP 代理的策略,你在使用 AOP 时指定使用的是 JDK Proxy 方式还是 CGLIB 代理方式,具体知识可以参考 spring aop 方面的内容。 JTester 插件的使用 插件功能 A、 debug 模式下保存变量的值为 xml 文件。 B、 实现 IDE 模式下的持续测试功能,现在分为运行整个项目的测试和运行改变代码的 测试。 C、 实现测试错误的在 eclipse ide 下以 marker 的方式标注出来。 D、 数据库测试,自动提取 dbfit 格式的数据 插件安装 eclipse插件的update url:http://core-sys-dept.alibaba-inc.com:9999/eclipse_plugin/jtester安 Caused by: org.springframework.beans.TypeMismatchException: Failed to convert property value of type [$Proxy13] to required type [com.ali.b2b.crm.base.i18n.I18nImpl] for property 'i18n'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [$Proxy13] to required type [com.ali.b2b.crm.base.i18n.I18nImpl] for property 'i18n': no matching editors or conversion strategy found at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanW rapperImpl.java:391) ... 63 more 装完毕,在eclipse系统preference中有如下选项可以设置 录制变量的功能 保存变量,在 debug 模式下,watch expression 或 variables 视图下,右键菜单多了个 Save Expression,如下图,单击就可以把变量转为 xml 保存到项目对应的 test resources 下相 应的 package 路径下。 运行在控制台显 示 jtester ct 的消 息,如果各位觉 的消息比较烦的 话,可以禁止消 息显示 是否运行整个 project 的测试, 可以在项目设置中重置 设置项目路径,现在只有 debug 保存变量时用到,可以在项目设 置中重置 运行 test 的 jvm 参数设置, 比如需要大内存是设为 –Xmx512m 重置系统级 jtester ct 的参 数设置 如果是跑项目中的测试是,运行 test 的 package 范围设定 Jtester ct 是自动运行的,但 ct 选项改变或 java 代码发生变化时,ct 就会在后台默默的运 行相应的 test 测试。 如果 test 有错误产生,插件会往代码相应行加上 marker,如下图。 dbFit 插件编辑 功能 从数据库中直接拖取数据 在 eclipse IDE 中将 jtester.properties 文件拖到 Connections 视图中 插件会打开一个数据库连接框(当然也可以手动打开数据库连接框) 在 driver jars 输入所需要的 jar 包路径,对于我们的 oracle 连接需要以下 3 个包 D:/alibaba/repository.project/headquarters/jdbc/proxy/headquarters-jdbc-proxy-1.1.jar D:/alibaba/antx/repository/Jakarta/commons/logging/commons-logging-1.1.jar; D:/alibaba/antx/repository/jdbc/oracle/ojdbc14.jar 为了方便,大家也可以在 JTester.properties 中增加一条配置 database.driverJar=D:/alibaba/repository.project/headquarters/jdbc/pr oxy/headquarters-jdbc-proxy-1.1.jar;D:/alibaba/antx/repository/Jakart a/commons/logging/commons-logging-1.1.jar;D:/alibaba/antx/repository/ jdbc/oracle/ojdbc14.jar 这样,连接框会自动解析 properties 文件列出 jar 的路径。 点击 finish 创建连接,展开表结构,可以在表或字段上右键菜单“表查询” 表数据有显示到 resultView 视图中,可以复制全表或选择数据行复制为 dbfit 格式的数据 然后就可以黏贴到 wiki 文件中了 使用 SQL Editor 编辑器 当使用默认的 select * from 选取的数据没法满足你的需求时,你可以使用 SQL Editor 来 查询并准备数据。 当执行对应的 sql 语句,结果同样会在 result view 中列出。 问题:为什么我执行 sql 语句时,弹出下列对话框 解决:因为你没有在 connection 视图中指定你 sql 语句执行的 schema,你可以在 connection view 的 schema 节点上右键菜单,选中一个默认的 schema。
还剩38页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

javaguy

贡献于2012-07-07

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf