JBuilder 2005 单元测试体验
白盒测试是软件工程中重要的概念,开发人员往往要花1/3时间测试自己编写的程序.一个类所有开放的API接口都必须通过白盒测试,JBuilder集成了目前流行的JUnit单元测试框架,提供了创建,运行,编译测试用例的支持.
自动测试代码的重要性
一个产品只有通过检验才能投放市场,同样的,一个业务类也只有在经验测试后才能保证功能的正确性,以便被其他类或程序调用,否则隐藏其中的Bug就蔓延开了.业务功能点测试是测试人员的职责,但业务类API的正确性必须由开发人员保证.
一个产品只有通过检验才能投放市场,同样的,一个业务类也只有在经验测试后才能保证功能的正确性,以便被其他类或程序调用,否则隐藏其中的Bug就蔓延开了.业务功能点测试是测试人员的职责,但业务类API的正确性必须由开发人员保证.
回忆一下最近你所开发的系统,往往一个最难忘的情节是通宵达旦地毯式搜索某个刁专的Bug,历尽千辛万苦,最终找到并解决了它.查找一个隐藏的Bug往往是踏破铁蹄无觅处,而找到后却是:解决全不费功夫.
造成这尴尬窘局有以下几点原因:
其一是使用增量式测试策略,即先编写功能代码,在模块开发完毕后才回过头来编写测试用例,因为一个功能模块可能包含许多相互关联的类,形成了层层调用,交错复杂的调用网络,一旦发现了Bug,只得查户口似的逐一排查,其艰辛程度可想而知.
其二是使用不正确的测试方法,如在每个类中提供一个main()测试函数,对类中的功能方法进行测试,通过运行类的main()方法查看类功能的正确性.在某种程序上,这或许是一个值得赞扬的工作习惯,但工作方式却不足取.因为每个类都必须单独运行,以执行其测试功能,并由开发人员观察测试的正确性.随着程序规模的扩大,类数目直线上升,原有的类也会发生代码的调整,一些功能点可能就变成了漏网之鱼,变成了茫茫"类"海里的黑户口,将来"违法乱纪"起来就很难监控了.
针对这些传统测试思想的不足,测试先行,频繁测试,自动测试的测试思想被越来越多的开发人员所接受并付诸实践.
测试先行乍听起来有点让人不可思议,一件东西还没有做出来就想着怎么去测试它 仔细分析,这并不荒唐,因为这让你在设计类时,站在调用者的角度去理解类的对外接口,迫使你深入理解类的外在关系,考虑接口的用途,而在具体编写程序时才去具体考虑内部实现细节,这样设计出接口将更易使用,结构也会更趋合理.
频繁测试,即指测试不应当是阶段性的工作,而应当在程序编写过程中不断进行.因为系统中的类之间往往都存在较多的关联关系,当更改了类的功能时,往往会有多个类受到直接或间接的影响.所以你应该频繁测试以及早发现这种因功能,调整而引起的Bug,越早发现错误解决它的代价越小.频繁测试也是XP编程的一个重要环节,XP编程总让人觉得他们注重功能实现而忽视测试,其实他们也非常关注测试,毕竟测试可以使他们尽可能快的稳步前进.
所谓自动测试并不是说有一个工具可以让你像安检器一样,自动测试出你类中的问题.而是指应用一定的测试框架,为每个业务类编写独立的测试用例,类代码调整后,对应的测试用例同步调整.多个测试用例组成一个测试套件一起批量运行,它们就像一个强大的Bug嗅探器,一旦发现Bug就会输出特定的信息报告错误,只要一个测试用例没有通过测试就说明程序中有问题.测试用例中所包含的测试规则完成由你定制,这个测试套件对Bug嗅探的"灵敏度"完全取决于测试用例的测试规则,框架提供编写和运行测试用例的规范性方法.
在编写一个业务类时,需要相应编写对应的测试用例,一开始挺招"惯性定律"抵触的,因为它要求你将创建一个测试用例类,似乎需要更多的工作.但你的付出是会得到加倍回报的,随着软件类规模的增大你会发现,当传统测试方法越来越捉襟见肘,穷于应付时,基于测试框架的测试技术依然"谈笑自如".当然别人这么说,我们也不应当马上就深信不疑,疑惑永远是值得推崇的科学精神,我们应该通过自己的实践却真真切切地体会这种改进所带来的快乐.
JUnit测试框架
JUnit是由Erich Gamma和Kent Beck开发的开源测试框架,JBuilder集成了这个框架并做了扩展.JUnit之所以流行并为广大的开发人员所推崇,一是它实战性强,功能强大,二是它实在简单.一个产品或框架要能有生命力,最好都具备这样的特点.
简单的框架
JUnit是由Erich Gamma和Kent Beck开发的开源测试框架,JBuilder集成了这个框架并对此做了扩展.JUnit之所以流行并为广大的开发人员所推崇,一是因为它实战性强,功能强大,二是因为它实在简单.一个产品或框架要能有生命力,最好都具备这样的特点.
简单地讲这个框架提供了许多断言(assert)方法,允许你设置测试的规则,如:assertEquals(),assertNull(),assertNotSame(),assertTrue()等方法,一个测试用例包括了多个断言,当运行测试用例后,JUnit运行器会报告哪些断言没有通过,开发人员就可顺藤摸瓜搞个水落石出了.而传统的测试方法需要将期望的结果用诸如System.out.println()等语句将过程信息打印到控制台或日志中,由开发人员观察输出信息以判断是否正确,现在这种"观察"的工作由JUnit的那些assertXxx()方法自动完成.
JUnit的测试框架类结构很简单,主要由3个类组成,其类图关系如下图所示:
图 错误!文档中没有指定样式的文字.JUnit测试框架类结构
·junit.framework.Test:测试接口.
·junit.framework.TestCase:测试用例类,业务类的测试用例类只需要承继这个TestCase,根据情况编写若干个public void testXxx()方法,在方法中通过assertYyy()定制若干测试规则就可以了.
·junit.framework.TestSuite:测试套件类,它可以将多个测试用例类捆绑在一起运行,也可以捆绑另一个测试套件.
测试固件(Fixture)
一个测试用例可以包含若干个testXxx()测试方法,测试用例测试一个或多个类API接口的正确性,当然在调用类API时,需要事先创建这个类的对象及一些关联的对象,这组对象就称为测试固件(Fixture),相当于测试用例的"工作对象".
我们在前面说过,一个测试用例类可以包含多个testXxx()方法,在运行时,每个测试方法都对应一个测试用例类的实例.当然,你可以在具体的testXxx()方法里声明并实例化业务类的实例,在测试完成后再销毁它们.但是,这么一来你就要在每个testXxx()方法中都重复这些代码,因为TestCase实例被运行时,依照以下步骤运行:
1.创建测试用例的实例.
2.调用setUp()方法,执行一些初始化工作.
3.运行testXxx()测试方法.
4.调用tearDown()方法,执行销毁对象的工作.
如果测试用例类中有多个testXxx()方法,且它们都需要使用到相同的一组对象,我们可以在setUp()中实例化这组对象,并在tearDown()中销毁它们.要编写测试固件,依照以下步骤进行:
1.创建TestCase类的子类.
2.在子类中声明若干个测试所用的对象.
3.覆盖setUp()方法,在方法中实例化这些对象.
4.覆盖tearDown()方法,释放这些对象的资源.
如下面即是一个简单的测试固件:
代码清单 错误!文档中没有指定样式的文字.测试固件
1. public class MoneyTest extends TestCase
2. {
3. private Money f12CHF;//12瑞士法郎
4. private Money f14CHF; //14瑞士法郎
5. private Money f28USD; //28美国美元
6. protected void setUp() {
7. f12CHF= new Money(12, "CHF");
8. f14CHF= new Money(14, "CHF");
9. f28USD= new Money(28, "USD");
10. }
11. protected void tearDown(){}
12. }
第3~5行声明了3个Money类对象(测试固件),在setUp()方法中实例化这3个对象(第7~9行),由于这些对象可以被垃圾进行直接回收,所以在tearDown()中不做任何操作.
测试用例(TestCase)
有了测试固件,就可以开始编写测试用例的测试方法了.当然你也可不需要测试固件而直接编写测试用例方法.下面我们在测试固件的基础上添加测试用例方法testMoneyBag(),代码如下所示:
代码清单测试用例方法
1. public class MoneyTest extends TestCase
2. {
3. private Money f12CHF;//12瑞士法郎
4. private Money f14CHF; //14瑞士法郎
5. private Money f28USD; //28美国美元
6. protected void setUp() {
7. f12CHF= new Money(12, "CHF");
8. f14CHF= new Money(14, "CHF");
9. f28USD= new Money(28, "USD");
10. }
11. public void testMoneyBag()
12. {
13. Money bag[]= { f26CHF, f28USD };
14. MoneyBag expected= new MoneyBag(bag);
15. assertEquals(expected, f12CHF.add(f28USD.add(f14CHF)));
16. }
17. protected void tearDown(){}
18. }
测试方法都必须以test为前缀,且必须是public void的,运行器以此为反射查找规则找到这些测试用例方法.在一个方法中可以包括多个assertYyy()方法,每个assertYyy()方法都是一个测试规则.像第15行的assertYyy()断言方法即为测试Money的add()方法和MoneyBag类正确性的测试规则.
你可以在MoneyTest中添加多个public void testXxx()方法,运行器为每个方法生成一个测试用例实例,分别运行.
测试套件(TestSuite)
如果每次只能运行一个测试用例,那么又陷入了我们前面所谈到的传统测试的窘境:手工去运行一个个测试用例,测试套件专门为解决这一问题而来.它通过TestSuite对象将多个测试用例组装成到一个测试套件,则测试套件批量运行.需要特殊指出的是,可以把一个测试套件整个添加到另一个测试套件中,就象小筐装进大筐里变成一个箧一样.
测试套件类也通过承继TestCase类实现,只不过它提供了一个public static Test suite()静态方法,在该方法中将多个测试用例捆绑组装在一起.一个典型的测试套件代码如下所示:
代码清单 错误!文档中没有指定样式的文字.测试套件
1. public class MoneyTestSuite extends TestCase
2. {
3. public TestSuite1(String s)
4. {
5. super(s);
6. }
7. public static Test suite()
8. {
9. TestSuite suite = new TestSuite();
10. suite.addTestSuite(MoneyTest.class);
11. suite.addTestSuite(MoneyBag.class);
12. return suite;
13. }
14. }
在第9行中声明并实例化了一个TestSuite,在第10,11行分别加入一个测试用例.你可以通过suite.addTest(Test t)方法添加一个套件.这样运行这个套件就可以自动运行所有测试用例的测试方法了.
测试运行器
JUnit提供了3个标准的测试运行器运行这些测试用例或测试套件,这3个测试运行器分别是:
·junit.textui.TestRunner:文本测试运行器.
·junit.awtui.TestRunner:使用AWT组件界面的测试运行器.
·junit.swingui.TestRunner:使用Swing组件界面的测试运行器.
下面是基于AWT组件的测试运行器,如下图所示:
图 错误!文档中没有指定样式的文字.AWT测试运行器
·Test class name:指定测试用例类和测试套件类.
·一个进度条:表示运行测试的执行进度,进度条下是正确,错误,失败的测试统计数.
·Error and Failures:列出了测试错误和失败的列表,点选其中的一个选项时,JUnit在窗口底部列出错误跟踪迹.
提示:
JBuilder提供了方便的运行测试用例和测试套件类的方法,你只须点击鼠标右键就可直接调用了.此外,JBuilder提供了一个JBTestRunner测试运行器,在功能和易用性上非JUnit提供的测试运行器所能媲美.JBuilder支持JUnit所提供的两个测试运行器:junit.textui.TestRunner和junit.swingui.TestRunner.
业务类介绍
为了便于讲解,拟通过两个简单的业务类引出测试用例,一个是分段函数类,另一个是字符串处理类,在这节里我们先来熟悉这两个业务类.
为了便于讲解,拟通过两个简单的业务类引出测试用例,一个是分段函数类,另一个是字符串处理类,在这节里我们先来熟悉这两个业务类.
分段函数类
分段函数Subsection类有两个函数,sign()是一个符号函数,而getValue(int d)函数功能如下:
当d < -2时,值为abs(d);
当-2≤d<2 且d!=0时,值为d*d;
当d=0时,值为100;
当2≤d时,值为d*d*d.
其代码如下图所示:
代码清单 错误!文档中没有指定样式的文字.分段函数
1. package chapter25;
2.
3. public class Subsection
4. {
5. public static int getValue(int d) {
6. if (d == 0) {
7. return 100;
8. } else if (d = -2 && d = 2
13. // if (d > 32) {
14. // return Integer.MAX_VALUE;
15. // }
16. return d * d * d;
17. }
18. }
19.
20. public static int sign(double d) {
21. if (d 0) {
24. return 1;
25. } else {
26. return 0;
27. }
28. }
29. }
在getValue()方法中,当d>32时,d*d*d的值将超过int数据类型的最大值(32768),所以当d>32时,理应做特殊的处理,这里我们特意将这个特殊处理的代码注释掉(第13~15行),模拟一个潜在的Bug.
字符串处理类
由于标准JDK中所提供的String类对字符串操作功能有限,而字符串处理是非常常用的操作,所以一般的系统都提供了一个自己的字符串处理类.下面就是一个字符串处理类,为了简单,我们仅提供了一个将字符串转换成数组的方法string2Array(),其代码如下所示:
代码清单 错误!文档中没有指定样式的文字.字符串处理类
1. package chapter25;
2. public class StringUtils
3. {
4. public static String[] string2Array(String str, char splitChar, boolean trim) {
5. if (str == null) {
6. return null;
7. } else {
8. String tempStr = str;
9. int arraySize = 0; //数组大小
10. String[] resultArr = null;
11. if (trim) { //如果需要删除头尾多余的分隔符
12. tempStr = trim(str, splitChar);
13. }
14. arraySize = getCharCount(tempStr, splitChar) + 1;
15. resultArr = new String[arraySize];
16. int fromIndex = 0, endIndex = 0;
17. for (int i = 0; i < resultArr.length; i++) {
18. endIndex = tempStr.indexOf(splitChar, fromIndex);
19. if (endIndex == -1) {
20. resultArr[i] = tempStr.substring(fromIndex);
21. break;
22. }
23. resultArr[i] = tempStr.substring(fromIndex, endIndex);
24. fromIndex = endIndex + 1;
25. }
26. return resultArr;
27. }
28. }
29.
30. //将字符串前面和后面的多余分隔符去除掉.
31. private static String trim(String str, char splitChar) {
32. int beginIndex = 0, endIndex = str.length();
33. for (int i = 0; i 0; i--) {
40. if (str.charAt(i - 1) != splitChar) {
41. endIndex = i;
42. break;
43. }
44. }
45. return str.substring(beginIndex, endIndex);
46. }
47.
48. //计算字符串中分隔符中个数
49. private static int getCharCount(String str, char splitChar) {
50. int count = 0;
51. for (int i = 0; i < str.length(); i++) {
52. if (str.charAt(i) == splitChar) {
53. count++;
54. }
55. }
56. return count;
57. }
58. }
除对外API string2Array()外,类中还包含了两个支持方法.trim()负责将字符前导和尾部的多余分隔符删除掉(第31~46行);而getCharCount()方法获取字符中包含分隔符的数目,以得到目标字符串数组的大小(第49~57行).
创建测试用例
JBuilder为测试用例类指定了一个默认的类名,即Test,包名和业务类包名一致.接受默认的值,直接按Finish创建TestSubsection测试用例类.
我们先为Subsection类创建测试用例.
1.在编辑器中打开Subsection.java文件,使其处理激活态.
2.File->New...->Test->在Test页的对象库中双击Test Case图标,启动创建测试用例的向导,如下图所示:
图 错误!文档
中没有指定样式的文字.指定测试的方法
·Select class:测试的目标类,默认为当前编辑器中打开的类,也可以通过其后的…按钮选择工程中其他的类.
·Avaiable methods:列出了测试目标类的所有public,protected和默认可视域的方法,private方法不列出.只要你测试了前三者的方法,private也被间接测试到了.这里,我们选择getValue()和sign()方法.
点击Next到下一步.
3.设置测试用例类的类名.
图 错误!文档中没有指定样式的文字.指定测试用例类的名称
JBuilder为测试用例类指定了一个默认的类名,即Test,包名和业务类包名一致.接受默认的值,直接按Finish创建TestSubsection测试用例类.
实战经验:
虽然在物理上,业务类和测试用例类被放在不同目录下,但在工程窗格的资源树中,业务类和测试用例还是挤在了一起.如果一个包下有多个业务类,加上它们相应的测试用例类,将显得更加拥挤不堪.所以最好将测试用例放到不同的包中,如com.super.bdbj包中的所有业务类的测试用例放到test.super.bdbj目录下,这样将彻底解决测试用例和业务类的物理和逻辑上的分离,使工程窗格中的资源树更加整洁明了.
TestSubsection类的代码如下所示:
代码清单 错误!文档中没有指定样式的文字.向导生成的TestSubsection类
1. package chapter25;
2.
3. import junit.framework.*;
4. public class TestSubsection extends TestCase {
5. private Subsection subsection = null;
6. protected void setUp() throws Exception {
7. super.setUp();
8. subsection = new Subsection();
9. }
10.
11. protected void tearDown() throws Exception {
12. subsection = null;
13. super.tearDown();
14. }
15.
16. public void testGetValue() {
17. int d = 0;
18. int expectedReturn = 0;
19. int actualReturn = subsection.getValue(d);
20. assertEquals("return value", expectedReturn, actualReturn);
21. /**@todo fill in the test code*/
22. }
23.
24. public void testSign() {
25. double d = 0.0;
26. int expectedReturn = 0;
27. int actualReturn = subsection.sign(d);
28. assertEquals("return value", expectedReturn, actualReturn);
29. /**@todo fill in the test code*/
30. }
31. }
在第5行声明了一个Subsection的成员变量,并在setUp()中实例化这个变量(第7行),在tearDown()中释放这个变量(第12行),其实这三部分就构成了一个测试固件.当然,由于我们的getValue(),sign()方法都是静态方法,所以并不需要这个固件,在测试方法中直接调用方法就可以了,如Subsection.getValue(),但为了加强概念上的认识,我们特别予以保留.
第16~22行的testGeValue()方法,和第24~30行的testSign(),就是在向导第1步所选择的需要测试的API方法对应的测试方法.JBuilder当然不可能知道我们API的逻辑规则,所以它仅提供了一个框架式的测试代码,需要我们发挥聪明才智通过assertXxx()定制覆盖性强的测试规则.
注意:
你也可以手工在TestSubsection类中添加测试方法,测试方法必须遵照public void testXxx()样式规范.所以如果你想在测试用例类中添加一个辅助性的方法,请不要以test为前缀,在更改业已生成的测试方法名称时,也要保证不去除方法前的test前缀,测试运行器籍此查找测试用例类中的测试方法.
下面,分别删除testGetValue()和testSign()方法体中的代码,用以下粗体代码替换之:
代码清单 错误!文档中没有指定样式的文字.添加测试规则后的TestSubsection类
1. …
2. public class TestSubsection extends TestCase
3. {
4. …
5. public void testGetValue() {
6. int d1 = -3,y1 = 3;
7. int d2 = -2,y2 = 4;
8. int d3 = 0 ,y3 = 100;
9. int d4 = 2 ,y4 = 8;
10. int d5 = 33 ,y5 = 32768;
11. int d6 = 33 ,y6 = Integer.MAX_VALUE;
12. assertEquals(y1,subsection.getValue(d1));
13. assertEquals(y2,subsection.getValue(d2));
14. assertEquals(y3,subsection.getValue(d3));
15. assertEquals(y4,subsection.getValue(d4));
16. assertEquals(y5,subsection.getValue(d5));
17. assertEquals(y6,subsection.getValue(d6));
18. }
19.
20. public void testSign() {
21. double d1 = -1.0, d2 = 0.0, d3 = 1.0;
22. int y1 = 1, y2 = 0, y3 = 1;
23. assertEquals(y1, subsection.sign(d1));
24. assertEquals(y1, subsection.sign(d1));
25. assertEquals(y1, subsection.sign(d1));
26. }
27. }
打蛇打七寸,擒贼先擒王,抓Bug还须在那些关键点上下功夫,特殊转换点是最容易出现问题的热点地区,需要给予特别的关注.所以我们为getValue()设置了6个测试点,而为sign()方法设置了3个测试点.
在工程窗格资源树中找到TestSubsection.java文件,右击在弹出的菜单中选择Run Test using "TestSubsection1",JBuilder利用JBTestRunner测试运行器运行这个测试用例,在信息窗格中出现如下的运行器界面:
图 错误!文档中没有指定样式的文字.JBTestRunner运行器界面
JBTestRunner窗口左边为3个标签页,它们的作用在卤淼乃得鳎 BR>
表 错误!文档中没有指定样式的文字.JBTestRunner窗口标签页说明
标签页图标
说明
如果测试没有错误,该标签页为默认显示的标签页.形成一棵测试套件->测试用例->测试方法3级的级联树.树中每个节点均有一个状态指示图标. 表示通过测试,而 表示未通过测试.点击未通过测试的节点,右边帧列出了错误跟踪迹,通过这个跟踪迹可以找到哪个测试规则(断言方法assertXxx())未通过.
测试错误页,如果发生了测试错误,该页被默认显示.列出所有发生错误的测试,该页是 标签页的子集.
该标签页中显示出所有输出到控制台的信息,如测试用例中有通过System.out.println()输出信息,则这些信息在此查看.
当没有发生失败的测试时,测试进度条显示为绿色,否则显示为红色.对于测试用例很少的情况你看不到它的效果,如果成百上千的测试用例一起运行,这个进度条的作用是显而易见了.进度条右边是测试结果统计信息:包括测试方法总数和成功数,统计信息右边是测试所花费的时间.
右帧是测试失败的引发点,单击链接,JBuilder自动定位到测试用例测试失败的地方,在本例里,JBuilder将定位到代码清单 错误!文档中没有指定样式的文字.添加测试规则后的TestSubsection类的代码的第17行,即:assertEquals(y5, subsection.getValue(d5));所在的行.
说明测试失败是由这个断言引起的,由于这个断言测试入参大于32时的函数返回值正确性,所以我们就知道程序没有考虑到返回值超过int类型范围的情况.返回到代码清单 错误!文档中没有指定样式的文字.前文分段函数代码中将第13~15行被注释的代码放出来,重新运行测试,你将发现测试全部通过了.
这也说明,编写一个有效测试用例并非易事,程序路径100%覆盖,完美的测试用例往往很难达到.当然,你可以依照一些经验性的原则,其中最大的一条就是:关注关键点.测试用例编写的技巧,超过了本文的范围,读者可以自行参考相关的书籍.
提示:
有时,JBTestRunner右边的帧窗口虽然内容已经超出,但滚动条却没有显示出来,想来应该是JBuilder一个小小的Bug吧.你可以在帧窗口中右击,在弹出的菜单中去除Word Wrap设置项,或者手工移动一下左右两帧间的分隔栏,滚动条就会显示出来了.
用套件捆绑运行多个测试用例
目前我们只为Subsection类生成了一个测试用例,在这节里,我们按照前述的方法,通过Test Case向导为StringUtils类创建一个测试用例代码框架,并编写测试方法,然后将这两个测试用例捆绑组合在一个测试套件中一起运行.
在这节里,我们按照前述的方法,通过Test Case向导为StringUtils类创建一个测试用例代码框架
目前我们只为Subsection类生成了一个测试用例,在这节里,我们按照前述的方法,通过Test Case向导为StringUtils类创建一个测试用例代码框架,并编写测试方法,然后将这两个测试用例捆绑组合在一个测试套件中一起运行.
选中StringUtils类,通过File->New..->Test,双击Test Case图标为StringUtils类的string2Array()方法创建测试用例,接受默认的测试用例类名TestStringUtils.
在向导生成的测试用例代码框架中,删除测试固件(因为是静态方法,没有必要用固件),即删除StringUtils的成员变量声明,setUp()和tearDown()方法.并在类中定义一个isArrayEquals()的方法,删除向导生成的testString2Array()测试方法体中的内容,编写自己的测试代码,其最终代码如下所示:
代码清单 错误!文档中没有指定样式的文字.TestStringUtils:StringUtils类的测试用例
1. package chapter25;
2. import junit.framework.*;
3. public class TestStringUtils extends TestCase
4. {
5. public void testString2Array() {
6. String str1 = null, str2 = "", str3 = "a", str4 = "a,b,c",str5 = ",a,b,";
7. String[] arr1 = null, arr2 = {""}, arr3 = {"a"}, arr4 = {"a", "b", "c"},
8. arr5 = {"", "a", "b", ""}, trimArr5 = {"a", "b"};
9.
10. assertNull(StringUtils.string2Array(str1, ',', false));
11. assertTrue(isArrayEquals(arr1, StringUtils.string2Array(str1, ',', false)));
12. assertTrue(isArrayEquals(arr2, StringUtils.string2Array(str2, ',', false)));
13. assertTrue(isArrayEquals(arr3, StringUtils.string2Array(str3, ',', false)));
14. assertTrue(isArrayEquals(arr4, StringUtils.string2Array(str4, ',', false)));
15. assertTrue(isArrayEquals(arr5, StringUtils.string2Array(str5, ',', false)));
16. assertTrue(isArrayEquals(trimArr5, StringUtils.string2Array(str5, ',', true)));
17. assertFalse(isArrayEquals(StringUtils.string2Array(str5, ',', false),
18. StringUtils.string2Array(str5, ',', true)));
19. }
20.
21. //判断两个字符数组是否相等
22. private boolean isArrayEquals(String[] arr1, String[] arr2) {
23. if (arr1 == null || arr2 == null) {
24. if (arr1 == null && arr2 == null) {
25. return true;
26. } else {
27. return false;
28. }
29. } else if (arr1.length != arr2.length) {
30. return false;
31. } else {
32. for (int i = 0; i Project Properties...->Run->在Run设置页中取消TestSubsection运行设置项的Context Menu选项,否则只会运行原TestSubsection的测试用例.
只包括10个类左右的小型项目工程也许无需用到测试套件,仅通过逐一单独运行测试用例来完成测试就可以了,但对于一个包含较多测试用例的工程,测试套件能给你带来极大的方便,它将多个测试用例捆绑在一起运行,达到一呼而百应的批量处理效果.
下面我们就来为TestSubsection和TestStringUtils这两个测试用例创建一个测试套件,并通过测试套件运行这两个测试.
1.File->New...->Test->在Test页中双击Test Suite图标启动创建测试套件的向导,如下图所示:
图 错误!文档中没有指定样式的文字.选择套件中捆绑的测试用例
在对话框列表中已经列出了工程中已有的两上测试用例类,你可以通过右边的Add...和Remove添加或删除测试用例.你可以通过Add Recursively,将指定目录下的所有测试用例一并加入.
点击Next到下一步.
2.指定测试套件类名.
图 错误!文档中没有指定样式的文字.指定测试套件类名
接受JBuilder为测试套件所提供的默认类名,按Finish完成该测试套件的创建,其代码如下所示:
代码清单 错误!文档中没有指定样式的文字.测试套件类
1. package chapter25;
2. import junit.framework.*;
3. public class TestSuite1
4. extends TestCase
5. {
6. public TestSuite1(String s) {
7. super(s);
8. }
9.
10. public static Test suite() {
11. TestSuite suite = new TestSuite();
12. suite.addTestSuite(chapter25.TestStringUtils.class);
13. suite.addTestSuite(chapter25.TestSubsection.class);
14. return suite;
15. }
16. }
测试套件类最主要的代码是suite()方法(第10~15行),首先在方法中声明一个TestSuite变量,通过addTestSuite()方法将TestStringUtils和TestSubsection测试用例捆绑在一起,你也可以通过这个方法添加其他的测试用例类.
TestSuite除可以将整个测试用例捆绑外,还可以捆绑另外的一个测试套件或一个测试用例中的测试方法:
l 添加一个测试套件
suite.addTest(suite_1)
l 添加测试用例某个方法
suite.addTest(new TestSubsection ("testGetValue"))
在工程窗格资源树的TestSuite1文件节点上右击,在弹出的菜单中选择Run Test using Defaults,JBuilder启动JBTestRunner,运行套件中捆绑的所有测试用例,其窗口如下图所示:
图 错误!文档中没有指定样式的文字.用测试套件运行组合运行多个测试用例
TestSuite下有两个测试用例类,测试用例节点下是测试方法节点.
创建测试固件
在测试用例中通过setUp(),tearDown()创建测试固件,只能使这个测试固件在单个测试用例的不同测试方法中共用,如果有多个测试用例都需要使用相同的测试固件,就需要将测试固件抽取到一个独立的类中
在测试用例中通过setUp(),tearDown()创建测试固件,只能使这个测试固件在单个测试用例的不同测试方法中共用
在测试用例中通过setUp(),tearDown()创建测试固件,只能使这个测试固件在单个测试用例的不同测试方法中共用,如果有多个测试用例都需要使用相同的测试固件,就需要将测试固件抽取到一个独立的类中.JBuilder提供了3个预定义的测试固件类,它们分别是:
·JDBC测试固件(JDBC Fixture):用于获取数据库连接的测试固件,用户仅需要通过设置一些数据库信息,就可以用方便的方法获取数据连接.
·JNDI 测试固件(JNDI Fixture):用于模拟从JDNI环境中获取对象的测试固件.
·比较测试固件(Comparision Fixture):将测试输出到外部文件中,以便第二次测试时进行比较.
·自定义测试固件(Custom Fixture):用户自定义的测试固件.
如果对JUnit的框架结构非常了解,也许这些JBuilder扩展的测试固件对你来说意义并不大,它们无非是构建一些常见的测试环境罢了,你完全可以自己编写.在本节里,我们介绍两个测试固件.
JDBC测试固件
如果你的工程中已经有一个获取数据连接的公共类,你也有必要构建一个JDBC测试固件,因为JDBC测试固件不但可以直接通过创建测试用例的向导直接指定,此外,JDBC测试固件还提供了许多面向测试的方法.
为了创建JDBC测试固件,我们先创建一个JDataStore的数据库,其数据文件位于/db/hr.jds,这个数据库的用户名和密码是:sysdba/123456.hr.jds数据库中有一张EMPLOYEE的表,其结构如下所示:
图 错误!文档中没有指定样式的文字.EMPLOYEE表的数据
EMPLOYEE有3个字段,分别是ID,NAME和AGE,分别是String,String和int类型,并按上图所示填入3条记录.
为了演示JDBC测试固件的具体使用,我们设计两个业务类:Employee和EmployeeDAO,尔后用JDBC测试固件为测试EmployeeDAO提供数据连接.这两个类的代码如下:
代码清单 错误!文档中没有指定样式的文字.Employee.java类
1. package chapter25.db;
2. public class Employee
3. {
4. private String id;
5. private String name;
6. private int age;
7. public Employee(String id, String name, int age) {
8. this.id = id;
9. this.name = name;
10. this.age = age;
11. }
12. public String getId() {
13. return id;
14. }
15. public String getName() {
16. return name;
17. }
18. public int getAge() {
19. return age;
20. }
21. public boolean equals(Object o) {
22. if (o instanceof Employee) {
23. Employee e1 = (Employee) o;
24. return id.equals(e1.getId()) && name.equals(e1.getName()) &&age == e1.getAge();
25. } else {
26. return false;
27. }
28. }
29. }
Employee类用于描述EMPLOYEE表的一条记录,该类访问数据库的EmployeeDAO代码如下所示:
代码清单 错误!文档中没有指定样式的文字.EmployeeDAO.java类
1. package chapter25.db;
2. import java.sql.*;
3. public class EmployeeDAO
4. {
5. private Connection conn;
6. public EmployeeDAO(Connection conn) {
7. this.conn = conn;
8. }
9. public Employee findById(String id) throws SQLException
10. {
11. String sqlStr = "select * from employee where id ='"+id+"'";
12. Statement stat = conn.createStatement();
13. ResultSet rs = stat.executeQuery(sqlStr);
14. if (rs.next()) {
15. return new Employee(id,rs.getString("name"),rs.getInt("age"));
16. }else{
17. return null;
18. }
19. }
20. }
为了节省篇幅,我们仅提供一个访问数据库的访问方法:findById(),即通过id查找Employee对象.
下面,我们利用JBuilder向导创建一个JDBC测试固件:
1. File->New...->Test->在Test页中,双击JDBC Fixture图标,启动创建向导,其对话框如下所示:
图 错误!文档中没有指定样式的文字.指定JDBC测试固件类名
在Class name中为JDBC测试固件指定类名:HrJdbcFixture,接受其他的默认设置,按Next到下一步.
2.设置连接数据库的信息.
在这步里,JBuilder提供了大部分数据库驱动程序的选择和连接信息设置,其对话框如下所示:
图 错误!文档中没有指定样式的文字.指定数据库连接信息
·Driver:选择borland.databstore.jdbc.DataStoreDriver类.JDBC测试固件提供了对大多数数据库的支持.其中下拉框中暂不可用的数据库驱动器类显示为红色,你可以通过配置工程扩展类库使它们可用.
·URL:点击其后的…按钮,弹出Create URL for DataStore对话框,如下图所示:
图 错误!文档中没有指定样式的文字.构造DataStore数据连接URL的对话框
该对话框的设置内容会随着数据库类型的不同而变化.对于JDataStore数据库,该对话框提供了两个选项,如果数据库文件放在本机上时用第一个设置项,否则用第二个设置项.我们选择第一种选项,点击其后的…按钮,导航到/db/hr.jds并选择之,按OK返回向导主对话框窗口.
·User name:sysdba.
·Password:123456.
按对话框下的Test Connection测试连接,应该会返回一个Success信息报告连接测试成功.按Finish创建JDBC 测试固件,其代码如下所示:
代码清单 错误!文档中没有指定样式的文字.HrJdbcFixture.java
1. package fixture;
2. import java.sql.*;
3. import java.io.*;
4. import com.borland.jbuilder.unittest.JdbcFixture;
5. public class HrJdbcFixture
6. extends JdbcFixture
7. {
8. public HrJdbcFixture(Object obj) {
9. super();
10. super.setUrl("jdbc:borland:dslocal:D:/JTJB2005/chapter25/db/hr.jds");
11. super.setDriver("com.borland.datastore.jdbc.DataStoreDriver");
12. super.setUsername("sysdba");
13. super.setPassword("123456");
14. }
15.
16. public void setUp() {
17. super.setUp();
18. }
19.
20. public void tearDown() {
21. super.tearDown();
22. }
23. }
JDBC测试固件承继了com.borland.jbuilder.unittest.JdbcFixture,这个类的重要方法包括:
·dumpResultSet():将一个ResultSet导到一个Writer中,该方法接受两个参数,一个是ResultSet另一个是Writer.
·getConnection():获取一个数据连接.
·runSqlBuffer():执行缓存于StringBuffer对象中的SQL语句.
·runSqlFile():执行保存在文件中的SQL语句,通过入参指定SQL文件的地址.
·setDriver():设置JDBC驱动器.
·setUrl():设置数据连接的URL.
·setUsername():设置用户名.
·setPassword():设置密码.
提示:
通过向导创建JDataStore的JDBC测试固件,虽然可以直接在对话框中选择com.borland.datastore.jdbc.DataStoreDriver驱动器,但运行这个JDBC测试固件时,JBuilder却报这样的错误信息:java.lang.ClassNotFoundException: com.borland.datastore.jdbc.DataStoreDriver.原来是JBuilder通过向导创建JDBC测试固件时,并没有直接将驱动器类加载到工程类库中,所以你需要手工通过Project->Project Properties...->Paths中,将JBuilder类库中名为JDataStore类库项加到工程类库中.
由于EMPLOYEE表的数据可能会随着测试的进行不断更改,这样在测试时测试规则就很难制定,因为规则的制定必须基于一个假设的环境.举个例子,我们现在要测试findById()方法,就必须知道EMPLOYEE表中有哪些数据,所以在测试开始时就必须创建好一些特定的数据.由于JDBC固件可以执行保存在外部文件中的SQL,所以我们创建一个insert.sql文件,将其放置在/db/insert.sql下,文件的内容如下:
delete from employee;insert into employee values('0004','大山',23);insert into employee values('0005','英华',30);insert into employee values('0006','柯明',31);
运行这个SQL语句时,先清空EMPLOYEE表中的数据,然后再插入3条特定的记录.下面,我们来创建应用JDBC测试固件的TestEmployeeDAO测试用例类.
1.在编辑器中激活EmployeeDAO.
2.File->New...->Test->双击Test Case图标启动创建测试用例的向导,在向导第1,2步为EmployeeDAO的创建一个名为TestEmployeeDAO的测试用例,这个测试用例对EmployeeDAO的findById()方法进行功能测试.
3.在向导的第3步选择测试固件,在向导对话框中我们前面创建的HrJdbcFixture已经出现在列表中.你也可以通过对话框的Add...和Remove选择不同的测试固件.
图 错误!文档中没有指定样式的文字.选择测试固件
按Finish直接创建TestEmployeeDAO的测试用例,其代码如下所示:
代码清单 错误!文档中没有指定样式的文字.TestEmployeeDAO.java,向导创建的测试用例类
1. package chapter25.db;
2. import junit.framework.*;
3. import fixture.*;
4. import java.sql.*;
5. public class TestEmployeeDAO extends TestCase {
6. private EmployeeDAO employeeDAO = null;
7. HrJdbcFixture hrJdbcFixture;
8. protected void setUp() throws Exception {
9. super.setUp();
10. /**@todo verify the constructors*/
11. employeeDAO = new EmployeeDAO(null);
12. hrJdbcFixture = new HrJdbcFixture(this);
13. hrJdbcFixture.setUp();
14. }
15.
16. protected void tearDown() throws Exception {
17. employeeDAO = null;
18. hrJdbcFixture.tearDown();
19. hrJdbcFixture = null;
20. super.tearDown();
21. }
22.
23. public void testFindById() throws SQLException {
24. String id = "";
25. Employee expectedReturn = null;
26. Employee actualReturn = employeeDAO.findById(id);
27. assertEquals("return value", expectedReturn, actualReturn);
28. /**@todo fill in the test code*/
29. }
30. }
测试用例在setUp()方法中实例化HrJdbcFixture对象,并调用其setUp()方法初始化环境.只有JDBC测试固件的setUp()方法执行后(第13),才可调用JDBC测试固件的其他方法,如getConnection()等,所以JDBC测试固件的setUp()是其初始化方法.下面我们对这个TestEmployeeDAO进行改造,改造的代码如下粗体代码所示:
代码清单 错误!文档中没有指定样式的文字.改造后的TestEmployeeDAO类
1. …
2. public class TestEmployeeDAO extends TestCase {
3. …
4. protected void setUp() throws Exception {
5. super.setUp();
6. hrJdbcFixture = new HrJdbcFixture(this);
7. hrJdbcFixture.setUp();
8. employeeDAO = new EmployeeDAO(hrJdbcFixture.getConnection());
9. hrJdbcFixture.runSqlFile("D:/JTJB2005/chapter25/db/insert.sql",true);
10. }
11. …
12. public void testFindById() throws SQLException {
13. Employee expectEmp = new Employee("0004","大山",23);
14. Employee realEmp = employeeDAO.findById("0004");
15. assertNotNull(realEmp);
16. assertEquals(expectEmp,realEmp);
17. }
18. }
因为JDBC测试固件需要在setUp()方法调用后,其他方法才可用,所以在TestEmployeeDAO的setUp()方法中,我们将EmployeeDAO的实例化方法移到后面,以便实例化EmployeeDAO时可以通过hrJdbcFixture.getConnection()获取数据连接(第8行).在第9行,执行insert.sql文件,清除表中原来的数据并插入3行测试数据.
在第13~14行,通过EmployeeDAO的findById()方法查找返回ID为0004的Employee对象,在第15~16行设定两个测试规则.
运行这个带JDBC测试固件的测试用例,HrJbdcFixture测试固件先准备好测试环境,然后再执行TestEmployeeDAO的testFindById()测试方法.
比较测试固件
比较固件用于记录下当前的测试记录,以便和下一次的输出比较.比较固件类继承于com.borland.jbuilder.unittest.TestRecorder,而TestRecorder类继承java.io.Writer.所以如果在测试时,需要用Writer输出信息就可以考虑使用比较固件了,它提供了许多易用的输出信息的方法.你可以通过向导来创建比较固件.
TestRecorder共有 4个记录模式的常量,它们分别是:
·UPDATE:比较固件将当前输出信息和已存在的信息文件相比较,如果文件没有存在则新创建一个文件,记录输出信息.
·COMPARE:比较固件将当前输出的信息和已经存在的信息比较.
·RECORD:比较固件记录当前输出的信息,如果原来已经有输出文件存在,覆盖之.
·OFF:关闭比较固件的功能.
注意:
在创建记录文件后,假设你更改了测试用例或测试套件,需要重新初始化这个输出文件:将TestRecorder的输出模式设置为RECORD,创建文件后再将其调整为UPDATE.输出的数据文件是二进制文件,放在和源程序文件相同的目录下且和测试用例类同名.
下面是测试固件常用的方法,介绍如下:
·boolean print(String s)
用TestRecorder打印一个字符串,如果模式为RECORD,且这个字符串和原来记录的不一致,则返回false.你可以设定这样的测试规则:
assertTrue(recorder.print(result.toString())
·boolean println(String s)
和print()相似,只不过添加一个换行.
·boolean compareObject(Object o)
调用传入对象的equals()方法和用前面用recordObject()记录的对象进行比较.
·boolean recordObject()
记录一个对象,以便后面调用compareObject()方法进行比较.
下面,我们创建一个比较固件,并应用这个比较固件为Employee类创建一个测试用例.
1.File->New...->Test->在Test页中,双击Comparision Fixture图标启动创建比较固件类的向导,其对话框如下所示:
图 错误!文档中没有指定样式的文字.指定比较固件名及属性
·Class name:测试固件类名,接受默认的ComparisionFixture1.
·Echo output to console:测试固件将信息同时输出到测试运行器的控制台上.
·Verbose output:测试固件将输出详细的信息.
此外,Save comparision data in this directory指定比较输出信息文件的存放位置,可以通过其后的…按钮更改,这里我们接受默认的设置.按OK直接创建比较固件类,你代码如下所示:
代码清单 错误!文档中没有指定样式的文字. 15 ComparisonFixture1.java,向导创建的测试固件类
1. package fixture;
2. import com.borland.jbuilder.unittest.TestRecorder;
3. public class ComparisonFixture1 extends TestRecorder
4. {
5.
6. public ComparisonFixture1(Object obj) {
7. super();
8. super.setMode(UPDATE);
9. super.setVerbose(true);
10. super.setEcho(true);
11. String fileName = super.constructFilename("D:/JTJB2005/chapter25/test",obj);
12. super.setOutputFile(fileName);
13. }
14.
15. public void setUp() {
16. }
17.
18. public void tearDown() {
19. }
20. }
第8行将模式设置为UPDATE,而第9,10行对输出属性作设置.第11~12行指定输出文件的目录.
2.创建TestEmployee测试用例类.
File->New...->Test->在Test页,双击Test Case图标启动创建测试用例向导,为Employee类和构造函数创建TestEmployee测试用例类.在向导的第1步,你将看到如下的对话框:
图 错误!文档中没有指定样式的文字.选择测试Employee类的构造函数
点击Next一直到向导的第3步:
列表中列出了工程的所有的测试固件,选择HrJdbcFixture,点击Remove删除这个固件,只留下ComparisionFixture1的固件,点击Finish直接创建TestEmployee测试用例类的代码框架,在代码框架基础上利用比较固件对Employee进行测试,其最终代码如下所示:
代码清单 错误!文档中没有指定样式的文字.应用比较固件的测试用例
1. package chapter25.db;
2.
3. import junit.framework.*;
4. import fixture.*;
5.
6. public class TestEmployee extends TestCase {
7. private Employee employee = null;
8. ComparisonFixture1 comparisonFixture1;
9.
10. protected void setUp() throws Exception {
11. super.setUp();
12. employee = new Employee("0004", "王五", 23);
13. comparisonFixture1 = new ComparisonFixture1(this);
14. comparisonFixture1.setUp();
15. }
16.
17. protected void tearDown() throws Exception {
18. employee = null;
19. comparisonFixture1.tearDown();
20. comparisonFixture1 = null;
21. super.tearDown();
22. }
23.
24. public void testEmployee() {
25. String id = "0004";
26. String name = "王五";
27. int age = 23;
28. comparisonFixture1.print(employee.getId());
29. comparisonFixture1.recordObject(employee);
30. employee = new Employee(id, name, age);
31. assertTrue(comparisonFixture1.print(employee.getId()));
32. assertTrue(comparisonFixture1.compareObject(employee));
33. }
34. }
在第12行实例化一个Employee对象,在第28行保持并打印出原employee对象的id值,在第31行进行比较;第29行记录原employee对象,在第31行进行两对象的比较.
运行TestEmployee类,在测试运行器的测试输出标签页中,你将可以看到输出的信息,在测试用例所在的文件夹下将创建一个无后缀名的输出文件Employee.
测试运行配置
在一般情况下,你不必为每个测试用例类单独创建一个运行配置项,JBuilder会采用默认的运行配置运行测试用例.
在测试用例向导的最后一步,允许你创建一个测试用例的运行配置项
在测试用例向导的最后一步,允许你创建一个测试用例的运行配置项,如下图所示:
图 错误!文档中没有指定样式的文字.创建运行测试项
在一般情况下,你不必为每个测试用例类单独创建一个运行配置项,JBuilder会采用默认的运行配置运行测试用例.
测试运行配置项在需要指定不同的测试运行器(默认为JBTestRunner),一次运行多个测试用例时,允许你进行特殊的定制.通过Project->Project Properties...->Run,在Run设置页中列出了工程所有已定义的运行配置项,Runtime Configurations列表中Type类型为Test的运行配置项即为测试运行配置项,可以通过对话框右边的Edit...按钮编辑这些运行配置项,如下图所示:
图 错误!文档中没有指定样式的文字.运行配置页列表
下面,我们来创建一个新的名为TestAllDbTest的运行配置项,用JUnit提供的junit.swingui.TestRunner测试运行器运行测试用例,使chapter25.db包下所有以Test打头的测试用例类一并运行.
在图 错误!文档中没有指定样式的文字.点击New...按钮,弹出New Runtime Configuration的对话框,如下图所示
图 错误!文档中没有指定样式的文字.TestSubsection运行
在Name中填入TestAllDb.在Type中选择Test,对话框下部的窗口调整为测试运行配置的界面.默认Class单选框选中,允许你指定一个测试用例类,由于我们希望通过该配置项运行chapter25.db包下所有的测试用例,所以我们选择Package单选框,并通过其后的…按钮选择chapter25.db包.
在Name starts with中填入Test,即运行所有类名以Test为前缀的类,此外,你还可以在Name ends with中指定类名后缀.
注意:
注意,如果包中有以Test打头的非测试用例类,运行这个测试运行配置项时,将会抛出错误,特别需要指出的是以Test为前缀的测试套件也是不允许的,满足过滤条件的类都必须是测试用例类.
在Test Runner中包含3个测试运行器,分别是JBuilder Test Runner,JUnit SwingUI Test Runner
和JUnit TestUI Test Runner.默认为JBuilder Test Runner,也即JBTestRunner,当然这个运行器功能最强大.对于好奇心强的开发人员来说,可能希望看看JUnit自带运行器的芳容,笔者也是其中的一位,所以这里,我们选择JUnit SwingUI Test Runner.
对于New Runtime Configuration对话框其它的设置项,可以通过Help按钮获取JBuilder的帮助.
在完成TestAddDb运行配置项的设置后,点击工具栏中 图标右边的向下箭头,在弹出的菜单中选择TestAddDb,如下图所示:
图 错误!文档中没有指定样式的文字.通过测试运行配置项运行测试用例
点击TESTALLDB项,JBuilder使用JUnit的junit.swingui.TestRunner运行chapter25.db包中的TestEmployeeDAO和TestEmployee两测试用例类,因为这两个用例类都以Test打头,运行器窗口如下所示:
图 错误!文档中没有指定样式的文字.junit.swingui.TestRunner测试运行器
在Test Hierarchy标签页中,列出了TestEmployeeDAO和TestEmployee两测试用例类,所以通过测试运行配置项,也可以实现测试套件相似的效果,当然还是没有办法达到测试套件的灵活度.假设测试用例命名没有规律,或者存在过滤冲突,这时就只得求助于测试套件了.
总结
JUnit的框架非常简单,只需要知道TestCase类的执行顺序,测试方法的命名规范就大体掌握了JUnit的测试框架,所谓测试固件和测试套件也是利用TestCase的这两个特征而提供的扩展技术.所以编写测试用例难的不在于框架的理解,在于测试规则的设置,而后者需要通过不断的实践总结才可以提升.
- jdbcfor2005 > JBuilder 2005 单元测试体验
-
JBuilder 2005 单元测试体验
下载该文档 文档格式:DOC 更新时间:2006-03-02 下载次数:0 点击次数:1文档基本属性 文档语言: Simplified Chinese 文档格式: doc 文档作者: jelly 关键词: 主题: 备注: 点击这里显示更多文档属性 经理: 单位: hit 分类: 创建时间: 上次保存者: 修订次数: 编辑时间: 文档创建者: 修订: 加密标识: 幻灯片: 段落数: 字节数: 备注: 演示格式: 上次保存时间:
- 下载地址 (推荐使用迅雷下载地址,速度快,支持断点续传)
- DOC格式下载
- 更多文档...
-
上一篇:香港工大学
下一篇:bpel-101-HelloWorld
点击查看更多关于jdbcfor2005的相关文档
- 您可能感兴趣的
- sqlserver2005jdbc jdbc连接sql2005 sql2005jdbc sql2005jdbc.jar sql2005jdbc驱动 jdbcsql2005下载 jdbcformysql javajdbc mysqljdbc sqljdbc4.jar
- 大家在找
-
- · 皮科克和怀斯曼
- · 心理学电子书txt下载
- · 清远市清城区王兴豪
- · 甲基苯丙胺如何结晶>湖南省普通高中学业水平考试要点解读
- · 平均分第二课时ppt
- · 计算机病毒的危害
- · 玻璃冰雕图库
- · 批判否定邓小平理论
- · txt格式斗破苍穹全集
- · 奥迪汽车故障案例
- · 长沙中南大学qq群
- · flash相册制作教程
- · 单片机控制led
- · dnf武器补丁下载
- · 无师自通韩国语下载
- · 建筑工程木工图纸
- · 110kv线路保护
- · cad2007教程自学网
- · 世界末日2012预言解密
- · 机械设计基础第五版答案
- · 3年级英语上册单词
- · 宁夏造价管理站
- · 百度仙侠记官方网站
- · 黄庭坚书法作品欣赏
- · losinggrip下载
- · 四氟滑板橡胶支座
- · 双踪示波器
- · frontpage2007下载
- · 单闭环直流调速系统
- · 天目手机维修论坛
- · mathtype6.0a
- · 博思人才网郑州
- · 阴阳历互换
- · cf无线电怎么发
- · 加强人才队伍建设
- · 法制宣传先进个人
- · 极品飞车9漂移指法
- · 安卓手机语音软件下载
- · 老版电驴免费下载
- · 数控技能大赛试题下载
- 赞助商链接