Java开发网 |
注册 |
登录 |
帮助 |
搜索 |
排行榜 |
发帖统计
|
您没有登录 |
» Java开发网 » Design Pattern & UML
打印话题 寄给朋友 订阅主题 |
话题被移动 | ||||
该话题已被移动 - why , 2003-06-22 00:21 如果您尚不清楚该话题被移动的原因,请参考论坛规则以及本版公告或者联系本版版主。 |
作者 | JUnit 实施 |
fjchenq
发贴: 233 积分: 20 |
于 2003-03-20 13:23
Design by Contract本是Bertrand Meyer(Eiffel语言的创始人)开发的一种设计技术。我发现在JUnit中使用Design by Contract会带来意想不到的效果。Design by Contract的核心是断言(assersion)。断言是一个布尔语句,该语句不能为假,如果为假,则表明出现了一个bug。Design by Contract使用三种断言:前置条件(pre-conditions)、后置条件(post-conditions)和不变式(invariants)这里不打算详细讨论Design by Contract的细节,而是希望其在测试中能发挥其作用。 前置条件在执行测试之前可以用于判断是否允许进入测试,即进入测试的条件。如 expectedWheels > 0, myCar != null。后置条件用于在测试执行后判断测试的结果是否正确。如 expectedWheels==myCar.getWheels()。而不变式在判断交易(Transaction)的一致性(consistency)方面尤为有用。我希望JUnit可以将Design by Contract作为未来版本的一个增强。 Refactoring Refactoring本来与测试没有直接的联系,而是与软件熵有关,但既然我们说测试能解决软件熵问题,我们也就必须说出解决之道。(仅仅进行测试只能发现软件熵,Refactoring则可解决软件熵带来的问题。)软件熵引出了一个问题:是否需要重新设计整个软件的结构?理论上应该如此,但现实不允许我们这么做。这或者是由于时间的原因,或者是由于费用的原因。重新设计整个软件的结构会给我们带来短期的痛苦。而不停地给软件打补丁甚至是补丁的补丁则会给我们带来长期的痛苦。(不管怎样,我们总处于水深火热之中) Refactoring是一个术语,用于描述一种技术,利用这种技术我们可以免于重构整个软件所带来的短期痛苦。当你refactor时,你并不改变程序的功能,而是改变程序内部的结构,使其更易理解和使用。如:该变一个方法的名字,将一个成员变量从一个类移到另一个类,将两个类似方法抽象到父类中。所作的每一个步都很小,然而1-2个小时的Refactoring工作可以使你的程序结构更适合目前的情况。Refactoring有一些规则: 1> 不要在加入新功能的同时refactor已有的代码。在这两者间要有一个清晰的界限。如每天早上1-2个小时的Refactoring,其余时间添加新的功能。 2> 在你开始Refactoring前,和Refactoring后都要保证测试能顺利通过。否则 Refactoring没有任何意义。 3> 进行小的Refactoring,大的就不是Refactoring了。如果你打算重构整个软件,就没有必要Refactoring了。 只有在添加新功能和调试bug时才又必要Refactoring。不要等到交付软件的最后关头才Refactoring。那样和打补丁的区别不大。Refactoring 用在回归测试中也能显示其威力。要明白,我不反对打补丁,但要记住打补丁是应该最后使用的必杀绝招。(打补丁也需要很高的技术,详情参看微软网站) IDE对JUnit的支持 目前支持JUnit的Java IDE 包括 IDE 方式 个人评价(1-5,满分5) Forte for Java 3.0 Enterprise Edition plug-in 3 JBuilder 6 Enterprise Edition integrated with IDE 4 Visual Age for Java support N/A 在IDE中如何使用JUnit,是非常具体的事情。不同的IDE有不同的使用方法。一旦理解了JUnit的本质,使用起来就十分容易了。所以我们不依赖于具体的IDE,而是集中精力讲述如何利用JUnit编写单元测试代码。心急的人可参看资料。 JUnit简介 既然我们已经对JUnit有了一个大致的了解,我希望能给大家提供一个稍微正式一些的编写JUnit测试文档的手册,明白其中的一些关键术语和概念。但我要声明的是这并不是一本完全的手册,只能认为是一本入门手册。同其他OpenSource的软件有同样的问题,JUnit的文档并没有商业软件文档的那种有规则,简洁和完全。由开发人员编写的文档总是说不太清楚问题,全整的文档需要参考"官方"指南,API手册,邮件讨论组的邮件,甚至包括源代码中及相关的注释。 事实上问题并没有那么复杂,除非你有非常特别的要求,否则,只需参考本文你就可以得到所需的大部分信息。 安装 首先你要获取JUnit的软件包,从JUnit下载最新的软件包。将其在适当的目录下解包。这样在安装目录(也就是你所选择的解包的目录)下你找到一个名为junit.jar的文件。将这个jar文件加入你的CLASSPATH系统变量。(IDE的设置会有所不同,参看你所喜爱的IDE的配置指南)JUnit就安装完了。太easy! 你一旦安装完JUnit,就有可能想试试我们的Car和testCar类,没问题,我已经运行过了,你得到的结果应该和我列出的结果类似.接下来,你可能会先写测试代码,再写工作代码,或者相反,先写工作代码,再写测试代码。我更赞成使用前一种方法:先写测试代码,再写工作代码。因为这样可以使我们编写 工作代码时清晰地了解工作类的行为。 要注意编写一定能通过的测试代码(如文中的例子)并没有任何意义,只有测试代码能帮助我们发现bug,测试代码才有其价值。此外测试代码还应该对工作代码进行全面的测试。如给方法调用的参数传入空值、错误值和正确的值,看看方法的行为是否如你所期望的那样。 你现在已经知道了编写测试类的基本步骤: 1>扩展TestCase类; 2>覆盖runTest()方法(可选); 3>写一些testXXXXX()方法; Fixture 解下来的问题是,如果你要对一个或若干个的类执行多个测试,该怎么办?JUnit对此有特殊的解决办法。 如果需要在一个或若干个的类执行多个测试,这些类就成为了测试的context。在JUnit中被称为Fixture(如testCar类中的 myCar 和 expectedWheels )。当你编写测试代码时,你会发现你花费了很多时间配置/初始化相关测试的Fixture。将配置Fixture的代码放入测试类的构造方法中并不可取,因为我们要求执行多个测试,我并不希望某个测试的结果意外地(如果这是你要求的,那就另当别论了)影响其他测试的结果。通常若干个测试会使用相同的Fixture,而每个测试又各有自己需要改变的地方。 为此,JUnit提供了两个方法,定义在TestCase类中。 protected void setUp() throws java.lang.Exception protected void tearDown() throws java.lang.Exception 覆盖setUp()方法,初始化所有测试的Fixture(你甚至可以在setUp中建立网络连接),将每个测试略有不同的地方在testXXX()方法中进行配置。 覆盖tearDown()(我总想起一首叫雨滴的吉他曲),释放你在setUp()中分配的永久性资源,如数据库连接。 当JUnit执行测试时,它在执行每个testXXXXX()方法前都调用setUp(),而在执行每个testXXXXX()方法后都调用tearDown()方法,由此保证了测试不会相互影响。 TestCase 需要提醒一下,在junit.framework.Assert类中定义了相当多的assert方法,主要有assert(), assert(), assertEquals(), assertNull(), assertSame(), assertTrue(), fail()等方法。如果你需要比较自己定义的类,如Car。assert方法需要你覆盖Object类的equals()方法,以比较两个对象的不同。实践表明:如果你覆盖了Object类的equals()方法,最好也覆盖Object类的hashCode()方法。再进一步,连带Object类的toString()方法也一并覆盖。这样可以使测试结果更具可读性。 当你设置好了Fixture后,下一步是编写所需的testXXX()方法。一定要保证testXXX()方法的public属性,否则无法通过内省(reflection)对该测试进行调用。 每个扩展的TestCase类(也就是你编写的测试类)会有多个testXXX()方法。一个testXXX()方法就是一个测试。要想运行这个测试,你必须定义如何运行该测试。如果你有多个testXXX()方法,你就要定义多次。JUnit支持两种运行单个测试的方法:静态的和动态的方法。 静态的方法就是覆盖TestCase类的runTest()方法,一般是采用内部类的方式创建一个测试实例: TestCase test01 = new testCar("test getWheels") { public void runTest() { testGetWheels(); } } 采用静态的方法要注意要给每个测试一个名字(这个名字可以任意起,但你肯定希望这个名字有某种意义),这样你就可以区分那个测试失败了。 动态的方法是用内省来实现runTest()以创建一个测试实例。这要求测试的名字就是需要调用的测试方法的名字: TestCase test01 = new testCar("testGetWheels"); JUnit会动态查找并调用指定的测试方法。动态的方法很简洁,但如果你键入了错误的名字就会得到一个令人奇怪的NoSuchMethodException异常。动态的方法和静态的方法都很好,你可以按照自己的喜好来选择。(先别着急选择,后面还有一种更酷的方法等着你呢。) TestSuite 一旦你创建了一些测试实例,下一步就是要让他们能一起运行。我们必须定义一个TestSuite。在JUnit中,这就要求你在TestCase类中定义一个静态的suite()方法。suite()方法就像main()方法一样,JUnit用它来执行测试。在suite()方法中,你将测试实例加到一个TestSuite对象中,并返回这个TestSuite对象。一个TestSuite对象可以运行一组测试。TestSuite和TestCase都实现了Test接口(interface),而Test接口定义了运行测试所需的方法。这就允许你用TestCase和TestSuite的组合创建一个TestSuite。这就是为什么我们前面说TestCase,TestSuite以及TestSuite组成了一个composite Pattern的原因。例子如下: public static Test suite() { TestSuite suite= new TestSuite(); suite.addTest(new testCar("testGetWheels")); suite.addTest(new testCar("testGetSeats")); return suite; } 从JUnit 2.0开始,有一种更简单的动态定义测试实例的方法。你只需将类传递给TestSuite,JUnit会根据测试方法名自动创建相应的测试实例。所以你的测试方法最好取名为testXXX()。例子如下: public static Test suite() { return new TestSuite(testCar.class); } 从JUnit的设计我们可看出,JUnit不仅可用于单元测试,也可用于集成测试。关于如何用JUnit进行集成测试请参考相关资料。 为了兼容性的考虑,下面列出使用静态方法的例子: public static Test suite() { TestSuite suite= new TestSuite(); suite.addTest( new testCar("getWheels") { protected void runTest() { testGetWheels(); } } ); suite.addTest( new testCar("getSeats") { protected void runTest() { testGetSeats(); } } ); return suite; } TestRunner 有了TestSuite我们就可以运行这些测试了,JUnit提供了三种界面来运行测试 [Text UI] junit.textui.TestRunner [AWT UI] junit.awtui.TestRunner [Swing UI] junit.swingui.TestRunner 我们前面已经看过文本界面了,下面让我们来看一看图形界面: 界面很简单,键入类名-testCar。或在启动UI的时候键入类名: [Windows] d:>java junit.swingui.TestRunner testCar [Unix] % java junit.swingui.TestRunner testCar 从图形UI可以更好的运行测试可查单测试结果。还有一个问题需要注意:如果JUnit报告了测试没有成功,JUnit会区分失败(failures)和错误(errors)。失败是一个期望的被assert方法检查到的结果。而错误则是意外的问题引起的,如ArrayIndexOutOfBoundsException。 由于TestRunner十分简单,界面也比较直观,故不多介绍。朋友们可自行参考相关资料。 JUnit最佳实践 Martin Fowler(又是这位高人)说过:“当你试图打印输出一些信息或调试一个表达式时,写一些测试代码来替代那些传统的方法。”一开始,你会发现你总是要创建一些新的Fixture,而且测试似乎使你的编程速度慢了下来。然而不久之后,你会发现你重复使用相同的Fixture,而且新的测试通常只涉及添加一个新的测试方法。 你可能会写许多测试代码,但你很快就会发现你设想出的测试只有一小部分是真正有用的。你所需要的测试是那些会失败的测试,即那些你认为不会失败的测试,或你认为应该失败却成功的测试。 我们前面提到过测试是一个不会中断的过程。一旦你有了一个测试,你就要一直确保其正常工作,以检验你所加入的新的工作代码。不要每隔几天或最后才运行测试,每天你都应该运行一下测试代码。这种投资很小,但可以确保你得到可以信赖的工作代码。你的返工率降低了,你会有更多的时间编写工作代码。 不要认为压力大,就不写测试代码。相反编写测试代码会使你的压力逐渐减轻,应为通过编写测试代码,你对类的行为有了确切的认识。你会更快地编写出有效率地工作代码。下面是一些具体的编写测试代码的技巧或较好的实践方法: 1. 不要用TestCase的构造函数初始化Fixture,而要用setUp()和tearDown()方法。 2. 不要依赖或假定测试运行的顺序,因为JUnit利用Vector保存测试方法。所以不同的平台会按不同的顺序从Vector中取出测试方法。 3. 避免编写有副作用的TestCase。例如:如果随后的测试依赖于某些特定的交易数据,就不要提交交易数据。简单的会滚就可以了。 4. 当继承一个测试类时,记得调用父类的setUp()和tearDown()方法。 5. 将测试代码和工作代码放在一起,一边同步编译和更新。(使用Ant中有支持junit的task.) 6. 测试类和测试方法应该有一致的命名方案。如在工作类名前加上test从而形成测试类 名。 7. 确保测试与时间无关,不要依赖使用过期的数据进行测试。导致在随后的维护过程中很难重现测试。 8. 如果你编写的软件面向国际市场,编写测试时要考虑国际化的因素。不要仅用母语的Locale进行测试。 9. 尽可能地利用JUnit提供地assert/fail方法以及异常处理的方法,可以使代码更为简洁。 10.测试要尽可能地小,执行速度快。 事实上,JUnit还可用于集成测试,但我并没涉及到,原因有两个:一是因为没有单元测试,集成测试无从谈起。我们接受测试地概念已经很不容易了,如果再引入集成测试就会更困难。二是我比较懒,希望将集成测试的任务交给测试人员去做。在JUnit的网站上有一些相关的文章,有空大家可以翻一翻。 JUnit与J2EE 如果大家仔细考虑一下的话,就会发现,JUnit有自己的局限性,比如对图形界面的测试,对servlet/JSP以及EJB的测试我们都没有举相关的例子。实际上,JUnit对于GUI界面,servlet/JSP,JavaBean以及EJB都有办法测试。关于GUI的测试比较复杂,适合用一整篇文章来介绍。这里就不多说了。 前面我们所做的测试实际上有一个隐含的环境,JVM我们的类需要这个JVM来执行。而在J2EE框架中,servlet/JSP,EJB都要求有自己的运行环境:Web Container和EJBContainer。所以,要想对servlet/JSP,EJB进行测试就需要将其部署在相应的Container中才能进行测试。由于EJB不涉及UI的问题(除非EJB操作XML数据,此时的测试代码比较难写,有可能需要你比较两棵DOM树是否含有相同的内容)只要部署上去之后就可以运行测试代码了。此时setUp()方法显得特别有用,你可以在setUp()方法中利用JNDI查找特定的EJB。而在testXXX()方法中调用并测试这些EJB的方法。 这里所指的JavaBean同样没有UI的问题,比如,我们用JavaBean来访问数据库,或用JavaBean来包裹EJB。如果这类JavaBean没有用到Container的提供的服务,则可直接进行测试,同我们前面所说的一般的类的测试方法一样。如果这类JavaBean用到了Container的提供的服务,则需要将其部署在Container中才能进行测试。方法与EJB类似。 对于servlet/JSP的测试则比较棘手,有人建议在测试代码中构造HttpRequest和HttpResponse,然后进行比较,这就要求开发人员对HTTP协议以及servlet/JSP的内部实现有比较深的认识。我认为这招不太现实。也有人提出使用HttpUnit。由于我对Cactus和HttpUnit 了解不多,所以无法做出合适的建议。希望各位先知们能不吝赐教。 摘自水木清华 关于 异常 。。。。 谢谢指教 |
话题树型展开 |
人气 | 标题 | 作者 | 字数 | 发贴时间 |
5825 | JUnit 实施 | fjchenq | 8993 | 2003-03-20 13:23 |
4667 | Re:好像关于测试得应该贴到ILDE版上才对 | mochow | 8 | 2003-03-20 16:35 |
5288 | Re:JUnit 实施 | liufancjsdn | 16 | 2003-03-20 21:22 |
已读帖子 新的帖子 被删除的帖子 |
Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1 客服电话 18559299278 客服信箱 714923@qq.com 客服QQ 714923 |