本文仅就单元测试而论,虽然是说的测试,但目的是驱动开发,不过也不是谈测试驱动开发,更象是对测试驱动开发时TEST FIRST这个过程中如何保证测试代码的正确性的理解和想法,当然有一些,我认为是通用的,不管是不是测试优先。而我目前接触最多的还是JAVA的单元测试,所以谈的东西还是以JAVA为主,举的例子都是和JAVA有关的。
另外前些天看到一个帖子有人问这样的问题,想到当初自己刚接触JUNIT单元测试时也有类似的困惑,现在有了一些经验,所以写下来,既是对自己经验的总结,也是希望能有人相互讨论提高。
首先是我认为要做到测试代码的正确性的几个要点:
一、TEST FIRST
二、只写出需求测试(注意,是目标代码需求,这个代码的用户当然是自己了,^_^)
三、不要为了测试而测试(这句话是和一个朋友聊天时他说是他和Kent Back交流时Kent Back提醒他的,这里我也不是很确定对这句话的理解的正确与否,因为理解一句话,上下文也是关键的,而我并不很了解我朋友同Kent Back谈话的具体内容和过程,不过这里还是作为一个要点谈谈自己的想法)
四、每次写一点(原子级)测试
五、Clean code that works,(当然包括测试代码啦,^_^)
六、对于一个应用框架,最好是针对这个框架先写一个测试框架(这其实是一个很具体的内容,不过现在JAVA在WEB方面用得很多,测试相对来说也比较难些,所以有这点)
七、时刻提醒自己TEST FIRST的目的。(我们的目的是驱动开发,而不是为了测试,呵呵,这点是前面第一点和第二点和起来一样,之所以还要单独列,只是再次提醒,所以这一点我后面不作详细阐述)
八、偷懒是程序员的通病,但是小偷懒就别了。(我有这样的观点:程序员的水平高低,其实从他偷懒的程度上是可以看出来的……^_^)
在开始具体来说上述要点之前,我想先写个例子,只是觉得应该写个例子,^_^,我的文采实在不是很好的,所以凭感觉的。
一个例子:
我有一个专门用于将数据库操作结果集(ResultSet)解析成一个DOM的Document对象的类,这个类可以根据给定一个XML配置模板中的一个定义节点(declare节点)的子节点(column节点)集合来解析ResultSet生成Document对象,其中每个column节点都定义了要从ResultSet中获取的某一个字段的属性,包括字段名、该字段在展示是是否可修改(editable)、是否有格式化模式属性(pattern)用于格式化该字段的数据等等;为了做得更通用,也可以依据ResultSet返回的ResultSetMetaData对象来生成column节点,再由column节点获取数据体,当然这样做有很大的局限性,比如该column节点的pattern、editable等等属性的设置都不会太灵活。这个设计,其最大的灵活性被放在XML配置模板上,由模板的定义获取数据,并且定义了数据的展示属性,而一旦column节点是根据给定的ResultSet来自动生成时,灵活性大大折扣,虽然在大多数应用中,都不会使用由ResultSet来自动生成,但是如果一开始并不能确定定义列时却是必须这样做,特别是在ResultSet输出的字段数量是变化的时候。问题终于出来了,最近有一个应用就是这样,首先是必须要使用从ResultSet获取定义节点(column),然后,在完成了所有的代码后,发现给定ResultSet中都存在冗余字段,这个时候,没办法,只能是修改程序来适应它了。
在遇到这个麻烦,并确定必须修改自己代码后(老实说,第一反应当然是让人修改SQL来去掉冗余字段了,因为最初的设计根本就是不能有冗余数据的,不过确实是大家都有本难念的经啊,SQL是不能改了,因为数据库端的实现使用了一个稳定的公共实现,庆幸的是冗余字段是相同的),我脑袋里蹦出的第一个念头是添加一个事件监听器,来监听这个应用中生成数据部分(为了叙述方便,姑且叫“dataBuilder”吧)的代码,一旦数据生成就触发ResultSet解析完成事件,然后我可以写监听器来处理解析完成的结果集(当然就是将冗余的数据CUT掉啦),这样以后再出现其它类似的状况,我可以通过添加新的监听器来过滤数据而不需要动原有的代码。SWEAT,看来这个想法还算可行,至少比写一个子类看起来简单多,以后修改也容易多,动手吧。(其实要注意这个事情的发生环境,首先是码本来都OK了的,而后来突然发现这个问题,而这个问题是需要立刻修改掉的,所以没有太多时间来仔细考虑,我总是犯这样的错误。)
这个时候我有两个选择,一是直接就写代码,一是先写个测试。当然,我选择的是先写个测试,之所以摆明了这二个当然是为了比较了。先说如果我先写代码,那么我就直接进入了目标功能的角色,因为现在似乎目标很明确,我要给dataBuilder添加一个能处理监听器的功能,在使用ResultSet解析器解析完成一个Document后触发解析完成事件,通知所有注册的监听器,并将解析完成的结果通过事件对象传递给监听器进行处理。在以前,我会立刻想到如何添加事件,如何处理事件列表,还有两个必要的接口,一个是监听器接口,一个是事件接口,一些简要的构思之后,不用多少时间就可以完成这些工作,然后就开始调试。
上面那么说,主要是为了对比,现在我是先写测试的。当然,如上所述目标现在似乎也很明确的,那么无论如何写个测试类吧,在写上必要的to do list之后,我开始想如果现在代码写完了,我要怎样来使用它呢。哦,对了,测试这个之前还要写个测试使用的监听器实现,这个实现可以很简单,把解析结果Document干掉好了,呵呵,验证结果还更容易,那就把它所有节点remove掉好了,^_^。(注:这里的测试还没有到主功能,只是先做测试监听器部分)不过这个时候,我突然觉得目前这个设计似乎还是有些麻烦(懒惰是程序员的通病,sweat,每次想偷懒都想起这句),要写监听器,还要让dataBuilder处理监听列表触发事件,虽然让过滤数据操作可以很独立地添加,而监听器的注册也可以通过XML配置文件来完成,但还是显得多余,好像目前这个需求只要一个Decorator模式,写一个修饰类来修饰ResultSet解析器就差不多了,以后需要新的功能,换Decorator就可以了,也可以相互嵌套来完成多个功能,这样的话,就不需要对dataBuilder动太多手脚了。sweat,还好自己没动手瞎忙(无论如何,测试优先让我重新认识自己的设计,以及目标,于是我义无反顾地抛弃了原来的想法)。新目标出现了,看来我需要一个解析器接口实现的Decorator类(注:这个ResultSet解析器类原本就是一个接口ResultSetParser的实现),我可以先写一个扩展解析器接口的抽象类来包装下,以后的Decorator实现都从这个抽象类继承,直接实现修饰内容就可以了(实际我一直认为,在Decorator模式中所有Decorator实现类都有一个父抽象类继承自修饰目标类的接口,其最主要的目的是使Decorator实现类功能更清晰,因为实际这个抽象类要包装的东西其实很少,这些移到子类中也完全可以,这样的话子类就是直接实现修饰目标类的接口了,效果一样,所以我认为这里有一个这样的抽象类统一由所有修饰类继承,更主要的是宣布,一个修饰目标类接口的实现类它是一个修饰类,这在阅读程序,以及使用该API上可以达到很好的效果。)。OK,就这么办(我总是很容易下决定呢,^_^)。
那么现在我该怎么来测试了呢,当然我同样需要一个Decorator类实现来作测试(还是按原定计划,把Document的子节点remove掉),准备好测试数据后,当然是检查结果集了,但是这个时候麻烦又来了,我不但要检查column节点是否被正确过滤,还要检查数据体是否正确,这样的话,如果我使用硬编码断言来测试,那么结果一定是一堆的断言,当然,我可以先写好一个正确结果集的XML文件,然后读取它来和处理结果来比较(唉,Document对象没有实现equals),不过这还不是最麻烦的,最麻烦的是,这样的测试将依赖数据库环境,这样的话我又要保存数据库环境才能保证在任何情况下我可以重现我的测试,这无疑是最麻烦的,老实说,以前之所以会不写测试(即使是现在也有时候不写,虽然我也清楚写测试的好处),主要就是数据库环境很难在任何时候很容易地建立(我有试过DBUNIT,它也不是很好,搭建环境的代码也不少),我晕(想偷懒好难啊,难道又偷偷地不写测试不成?)。
写测试是一个很奇怪的过程,大脑的思维方式和直接写应用代码好像有些不同,呵呵,没搞清楚,我想这是咱和大师的区别吧,还只是只可意会不可言传,我一直的观点是,真正懂的人是能将模糊的东西解释成清晰的概念的。无论如何在我准备开始动手写这痛苦的测试代码时,我又有新想法了,为什么不在开始就不获取冗余数据呢,虽然Decorator模式在这里的应用很具有吸引力,因为它本身毕竟够简单明了,不过,同时想到Document对象本身的一些特点,首先它是大对象(呵呵,感觉上DOM的对象都好大哦,但其实还没有真正理解它到底是怎么个大法),其次,如果是过滤数据,那么势必要遍历该Document的节点,这样的话似乎处理起来的效率不怎么样,这怎么都不如在一开始就不从ResultSet中获取冗余数据来得简单明了。于是想到,在自动生成column节点上现在还是很弱的,可以用Builder模式来重构它,这样的话,可以通过替换column节点的builder来实现过滤,而且目前来说,也确实是只有自动生成column节点的情况才需要过滤冗余数据,所以也不需要考虑由XML配置模板定义好columns节点的状况;而原来默认的生成方式可以立刻就被提炼出来作为默认builder;这样添加的代码实际也很少,我想几乎更少些吧,因为原来在处理冗余数据时需要遍历Document,而现在只要认准了从ResultSetMetaData获取的字段名,对需要过滤的字段不生成column节点就Ok,而有新的变化出现时,只要替换重新实现builder接口就可以了。当然了,原来这个ResultSet解析器是有测试类的,也有相关的测试,那么将原来生成column部分代码提炼出来作为默认的builder后,可以跑以前的测试,啊哈哈哈哈,这几乎不费吹灰之力,ECLIPSE的重构功能很容易将生成column的代码提炼成一个方法,然后把这个方法提升成一个接口,接着如此如此(不用说了)就弄好了一个默认builder实现,然后解析器加个builder类属性,同时生成它的set方法,一切ko后跑测试(这里都是工具自动做的多,我这一步跨的是会大些的,不过多跑测试总是没什么坏处的,反正跑一次也就几秒中,也许还不用呢,^_^),一路绿灯。重构完成,sweat(注意到了吧,到目前为之,其实我没有写测试,而是使用了原来的测试代码,而代码部分也多是自动生成的。最重要的是,这里使用builder模式,其接口也被测试过了,所以后面加的builder实现就可以不考虑这点了,而只是完全的功能需求测试,功能需求总是可以很单一的,自然就简化了测试代码)。
现在又回到开始了,我的目标又换了,呵呵,新目标:一个builder接口实现,过滤冗余的字段。如何测试呢?一个ResultSet做输入参数是必要的,输出的话要没有冗余数据,实际应用中要过滤的字段名是固定的(呵呵,别忘了我的最初的目的,是过滤由于SQL查询使用公共接口导致有相同的冗余字段出现。),我可以SELECT出来一个空结果集,其字段都是我要过滤的那些字段名(这里,如前所述,数据体不是我要关心的内容,因为数据体的是根据column节点生成的,所以我只要检查column节点对不对就可以了,这样也简化了我的测试代码),当然,在这些之前是重置ResultSet解析器的builder对象为将要实现的目标代码类,很快就完成了所有的代码,跑测试,出错,修改,跑测试,Kent Back的测试模式显得那么简单,最后,终于一路绿灯了。呼………..
呵呵,当然不忘了最后一步,Clean code that works。我对目前这个状态下去提炼原代码,有个比喻:关起门来打狗。这时的重构真可以说瓮中捉鳖十拿九稳,最不济也只是回到原地,不过通常的结果总是出人意料的要。不管如何,开始重构,几个关键重构:首先默认的builder可以用一个Singleton模式,当然了,实现过滤冗余字段的builder也是,因为这个生成column节点的代码是不受多线程影响的;然后过滤冗余字段的builder实际可以是一个默认builder的Decorator,三下五除二搞定。最后测试通过,CHECK IN代码。
这是我的一次经历,可以看到,早先的测试代码也起了作用,所以,只要代码还要跑,那么测试代码永远也不会多余。而要保持清晰的测试代码,不仅需要清晰需求分析、设计以及良好的原有目标代码,一点点偷懒的精神,也是必要的,^_^。而在需求分析和设计这里,测试优先有一种不可抗拒的力量让你深入需求,特别是让你作为你的目标代码的用户来考虑这些代码的使用。本来这个例子只想提下,没想到写出来这么多,sweat。
OK,下面开始逐一讨论:
一、TEST FIRST
TEST FIRST其核心不在“TEST”,而是“FIRST”。测试驱动开发的目的是开发,而测试优先是其过程,从测试开始,以测试结束,中间是小增量的迭代开发,测试代码和开发代码同步进行(注:其实我认为测试代码也是开发代码,不过为了区分这2者,下面所说到的开发代码都指不包括测试代码部分;另外,这句话本身要是仔细扣是有问题的,并不全面,只是测试驱动开发的一个方面来说)。
不是说要保证测试代码的正确性就要TEST FIRST,而是TEST FIRST可以最大限度地保证测试代码的正确性。(这里要说回代码本身,无论是测试代码还是开发代码,无非是一种开发语言的代码,要保证代码的正确性,我认为主要是良好的设计、清晰的结构以及语言的合理使用,其中保持代码结构清晰是最容易做到的,简单的缩进,统一的编码风格基本就算合格了。对于测试代码,也是一样的道理。)如果不先写测试,那么一定是有目标代码了(废话好像,^_^),这在写测试代码时必然会受到限制,别说自己可以不理会写好的目标代码(我想90%的人还是比较难做到的吧,都写好了,还改来改去,而且一个接口一旦公布出去,由不得你想改就改),写测试的时候,束手束脚,这不能动,那不能改,你怎么办,当然是按照自己写开发代码的思路来写测试了(呵呵,好像如果是先写了开发代码而后写测试代码,测试代码的目的也多是为了验证开发代码的正确性而已,至少以前我有这样做时是这样的状况),于是你的思维被禁锢了,测试代码的好坏(这里的坏不是说不能用,^_^),就由开发代码控制了。说回到我举的例子,如果开始的时候确实是因为时间关系、以及需求的突发性改变导致没有很细致地考虑如何修改设计(实际上我认为这样的状况实在太多,我接触的用户,有很确定地提出某个需求,而最后由于来看演示的用户完全否决该需求而导致完全推翻重来的,不过要说的是,我面对的用户实在没法子,人改需求的用户比当初提需求的用户官大,sweat,不改不行,时间还不能拖,原因就不细说了),直接就由开始的构思来写好目标代码先,那么会是什么样的局面?首先是时间已经花进去了(呵呵,单就这个例子当然其实也没多少时间的,是相对来说的),而到开发代码完成要写测试的时候必然导致写测试的目的是验证这段代码的正确与否,这样的话,不用说,准备好输入数据,验证输出数据嘛,哪还管那么多?(我认为是这样的,一旦确定了目标,而且还是那么实在的目标,做起事来的时候,脑袋里早就计划好了,就没有余地来考虑其它东西了,更不要说跨出这件事情考虑做另一件事情了)。而采用测试优先的方式的话,其结果和过程我在例子里面也写的很详细了,没有目标代码写测试其实是很轻松的事情,因为现在什么都还没有,自己想怎么就怎么样,想让测试代码清晰、简单明了,那还能复杂到那里去?
所以说,TEST FIRST是提高测试代码正确性的一个很好的途径。这里先写开发代码后写测试代码的方式,其实说白了,是我想说的第三点,那样的测试,真的就很容易成了为了测试而测试的代码(记忆里,以前我这么做的时候,还都是为了测试而测试,呵呵,不知道其他朋友会如何)。
二、只写出需求测试(注意,是目标代码需求,这个代码的用户当然是自己了,^_^)
前面说到,要保证测试代码的正确性,从保证代码本身的清晰来入手,只写需求测试也是一个道理,不过要把它放在这里,主要也还是因为自己以前写测试的时候,往往都很容易忘记这一点,简单的比方说,当一个测试会牵涉多个类的时候,本来其实也很明确的,单元测试嘛,你测试你的目标类就OK了呗,但是每个人的认识不同,写测试的水平不同,更在于小心谨慎,总是在写目标类测试的时候,想这个地方虽然是A类做的事情,不过我也连带着加个断言看下A做对了没有吧,反正也就多加一句,以防万一嘛(太小心了,其实过界了都不知道)。别小看一行代码,我的感觉,超过20行(空行不算,sweat)的一个方法在你写完这个方法几个月后回来看,晕的几率很大,至少要立刻明白这个方法的内部结构是基本不能。积少成多,如果目标类关联了A、B、C、D、E五个甚至更多,多出来的恐怕就不是一、二行代码了,而且你一次小心一定会多次小心的,比如,在一个测试用例中(我所指的测试用例不是指TESTCASE类,而是该类中的一个TEST方法)你的断言中包含了对A的一个操作结果的测试,那么在另一个测试用例中,如果还用到A的这个操作,我肯定你还会写这个断言,因为你第一次的小心不是偶然。人说,小心使得万年船,不过,杞人忧天就过犹不及了。而写测试的时候,是很容易就会犯这样的错(唉,其实是我当初很容易犯这样的错,sweat)。
所以,职责分明,只写你需要的你该做的测试,别一开始就怀疑这个怀疑那个,处处小心,步步为营。我们要如何使用我们的代码,那么就写那样的测试,现在我都是拿测试代码当例子,写了一个公共模块,除了有文档以外,都是建议要是觉得看文档麻烦就看测试代码如何使用,或者你干脆就拷贝我的测试代码过去改下用好了。
三、不要为了测试而测试(这句话是和一个朋友聊天时他说是他和Kent Back交流时Kent Back提醒他的,这里我也不是很确定对这句话的理解的正确与否,因为理解一句话,上下文也是关键的,而我并不很了解我朋友同Kent Back谈话的具体内容和过程,不过这里还是作为一个要点谈谈自己的想法)
不要为了测试而测试,^_^,虽然说要写测试,不过大家都是开发的,如果你的测试代码的目的成了测试,我还是会说你这就不够“专业”了。(这句有点耳熟,星星:拜托,虽然大家都是中国人,你要是抄我台词,照样要告你剽窃)
其实开始我听朋友讲这句,似懂非懂,因为毕竟只是一句没头没尾的话,听过就算了。但是细细品了,确觉得有些道理(^_^,不知道我的想法是不是Kent Back说这句话的本意)。言归正传,开发人员的测试代码,还是不要以测试目标代码为目标的好,这样才能更好的确保测试代码的正确性。
这里要澄清的是,不是说测试代码就不要去拿来测试了,呵呵,测试代码的结果(无论是否测试先行)其实都是一样的,都是可以自动(或者半自动,^_^)执行的测试,我说了,是写测试的目的,由于你的目的不同,中间的过程也是大不相同的,对于开发人员来说,开发代码是最终目的(什么需求分析、系统设计啦等等等等,还不是为了能满足用户需求的代码出来给用户跑嘛,哦,当然,最最终目的,咱还是靠这个养家糊口,sweat),所以开发过程中的单元测试代码,它只是辅助我们开发的(这点好像说了好多遍了,sweat,别丢我臭鸡蛋),所以这些测试代码的最终目的也是为了开发代码,它和测试人员写的测试代码有本质的区别(测试人员要是来看我这篇文章,嘿嘿,SORRY啦,真不是为测试写的)。
说得好绕口,不过,只有清楚了这点,那么前面2点才会站得住脚,如果一开始得测试代码就只是为了测试目标代码得功能,那么TEST FIRST就是笑话了,连目标都没有怎么可能测试,而根据需求写测试,根本不管内部逻辑以及边界条件等等等等什么这覆盖那覆盖的测试,测试人员一定说我瞎掰了,这也能拿来测试?
所以,要保证测试代码的正确性,别为了测试而测试,只有不为了测试而测试,你才会真正做到第一、二点。(好像这点应该摆第一去,sweat,不过既来之则安之落)
四、每次写一点(原子级)测试
这一点,其实说白了,是XP中的小增量迭代,测试驱动开发强调虽然是测试优先,但是测试开发并行过程中相互的交互作用是很关键的。
一个很笼统的测试用例,既测试这个又测试那个,难免会复杂起来(量的积累带来质的变化),所以最好是一个测试用例只针对一个需求写测试,少量多次嘛。添加一点测试,运行失败,修改代码,运行测试,直到成功,然后重构代码,最后测试通过,是测试驱动开发的一个小迭代周期,当然迭代周期的控制是可以因人而异的。
每次写的测试越少,那么表示你关注的问题或者需求就越少,那么精力就越集中,相对的测试的代码量也越少,大问题通常都可以通过将其拆分成多个小问题分别解决来得到解决,如果觉得测试代码太复杂,自然也可以通过拆分复杂的测试代码为多个小的相对简单的测试代码段来缩小其复杂性。
五、Clean code that works,(当然包括测试代码啦,^_^)
再好的设计,架不住你混乱的代码,最简单的,没有良好缩进格式的代码,看起来真是恶梦,现在工具这么好,都能自动化了,不过我还是能在工作中看到很多没有格式化好的代码,sigh,最起码的要求都做不到,更不要说让他去重构代码了,估计写测试也是没戏,TEST FIRST更是免谈了。
所以别看这句话简单,做到了再体会才能理解。(这里又要唠叨下TEST FIRST,如前所述,我其实开始的时候是先写开发代码后写测试的,每次都很犹豫,是不是先写测试,而很多时候都架不住自己脑袋里那个已经“成型”的设计,要立刻写开发代码,测试嘛,总是认为后面再来也一样,不过在我多次逼自己先写测试之后,经历感觉完全不同,TEST FIRST根本不是原先自己所想的那样简单,所以,虽然我这里说了一堆它的好处,还是希望没有体验过的朋友别管那么多,试试先,一定会喜欢上它的,不过,要真正到处都使用它,还是要提高写测试代码水平的,因为测试代码不是那么简单哦,特别是和数据库和WEB搭上关系后)。
好了,这一点,其实我也不用再详细说了,其实大家瞎子吃馄饨心里有数。至于怎么做到,嘿嘿,看看《测试驱动开发》吧,也许会有体会的,而我这里就超出范畴了,关于这点,我也就此打住,因为人大师的金玉良言更精辟更有说服力,我也不想在这里做转贴。
六、对于一个应用框架,最好是针对这个框架先写一个测试框架(这其实是一个很具体的内容,不过现在JAVA在WEB方面用得很多,测试相对来说也比较难些,所以有这点)
这一点是说到应用上了,^_^,其实也许这才是一些朋友希望交流的东西,因为开始我学写测试的时候,真不是我不想写啊,而是实在不知道怎么写,对一个复杂的应用,比如前端WEB,后台数据库,只要和这2者关联起来,再简单的需求写起测试来也是一大堆代码,有的甚至就根本不知道怎么测试。我现在也还是对数据库端的测试很惧怕,希望借此有朋友能讨论下。
这里,我的观点是很明确的,一个应用框架,要有针对这个框架的测试框架来支持,才能让写出来的测试代码清晰以保证其正确性。测试框架的目的是要保证测试环境的正确搭建,这里说说题外话,测试环境,我认为是测试的关键,也是单元测试中最难的一个方面。对于一个接口的测试(纯粹的接口,不是实现类),可能就需要写一个扩展这个接口的实现类来辅助测试(我举的例子中,在开始考虑使用Decorator首先就是考虑的这个模式应用后的接口测试,这样我在测试具体的Decorator实现时才能抛开这个模式而单独测试实现类,而最后在column节点处理时用builder模式时,其实是拿默认的builder来做了这个测试的),这样做是我说的第二点;应用框架通常由接口架构,抽象出具体应用的接口,而具体应用则通过扩展该接口来实现功能,比如常见的WEB框架Structs,它的ACTION就是这样的接口,要做好对它的测试,框架本身的测试必不可少,但具体应用时其实和框架关系不大,只要制作符合要求的ACTION就可以了,这样的话,对ACTION的测试才是一般应用程序员要考虑的。对ACTION的测试,一种简单的方式是将ACTION中的要测试代码和STRUCTS框架隔离,这样就不需要访问STRUCTS框架,直接测试,但是,这样做其实不完整(不过这样做也有好处,就是将逻辑代码强制写在其它地方,而不写在ACTION中,当然ACTION本身就不是用来写这些的,不过我看到的,很多程序员其实是习惯于直接在ACTION中写业务逻辑代码的,这样的好处很少,就是眼前少写几行代码,但是以后就不见得了,这是我的第七点,大偷懒和小偷懒的区别),最好的状况是完整地测试ACTION类,从调用它开始,但是这样地话,准备测试环境会很麻烦,准备一个HTTP的REQUEST对象不是随便可以NEW出来的,会让人望而却步,要是只写一、二个这样的测试也就罢了,实际确实成百上千的,所以这个时候就需要一个测试框架了(呵呵,忽然想到,开始说测试框架,可能会有人误会,说是测试应用框架本身的代码,那样的测试当然不可少,不过这里说的测试框架,是指针对应用框架开发时要写的TESTCASE的框架),对于WEB测试有HTTPUNIT是不错的选择,不过还是比较低级的(呵呵,这里和语言一样的比喻,JAVA之类是高级语言,不是说它比汇编就高级),要针对STRUCTS框架写一个测试框架(现在有开源的这个框架),让测试环境的搭建工作简化到最少,这样真正写测试时才能只关注需求部分的测试代码,而测试代码本身也看起来不那么庞大。
七、懒惰是程序员的通病,但是小偷懒就别了。(我有这样的观点:程序员的水平高低,其实从他偷懒的程度上是可以看出来的……^_^)
第六点中已经带到了这个话题,其实只是调侃得说说自己的想法,不能算很正式的,因为这点其本质和测试没有一点关系。
我很懒的,要不然不会选择这个职业,因为程序可以自动帮我做事,这样我就可以只是轻松地看着了,^_^。
但是懒惰有很多种,有一种最直接的,只顾眼前的懒惰,还是拿不格式化代码来说,写的时候很随意,是好像让你少做了很多事,但是让看的人,让以后自己看的时候(特别是在查错时),付出更高的代价,我想,无论你在下次回头看这个代码的时候,理解它的时间是多花了多么少的时间,也比不上选个菜单自动格式化好代码花的时间少的。为变化而设计,正是为了将来能够更偷懒。有一个观点,现在我也想不起来是哪里看到了的,似乎哪里都有(《重构》啦、XP啦、测试驱动开发啦等等),就是在给一个已有模块添加新的功能前先重构已有代码,让已有代码适合于添加这个新的功能(好像第一次看到这个应该是《重构》一书),重构不是说让代码更好看而已,其目的就是为了适应变化,变化本来显得很大,但是可以通过重构来改善已有代码,让已有代码适合于新变化的添加,那么变化的复杂性就被缩小了。
所以我说,小懒惰就免了,别影响大局,代码清晰、必要的重构,不作这些只是小懒惰,它们是以将来可能更大的付出为代价的。测试代码嘛,也只是代码而已。
好了,终于写完了,中间几点难免有些雷同了(Clean code that works好像就可以涵盖所有了),还是总结下,思来想去,还是这句了:Clean code that works。大师的话就是精辟啊。不过,仔细想想,上面说的几点,好像都是没有说如何做到的,只是说了做到这几点可以极大得保证测试代码的正确性,但是这些关键点,要如何做到,我想大家还是多看看书吧,《测试驱动开发》、《重构》还有《极限编程-拥抱变化》这些都是难得的好书,我想,相对其它的书,这3本关键并不在于其有什么很高深的理论或者技术,而是它们都是指导程序员实践,如何从小处着手做程序,养成好的编程习惯,也更象是这些大师们自己手把手教咱写程序一样,难得的是文笔还很好,叙述得浅显易懂。