也谈谈测试驱动开发与敏捷

  看了聊天记录,并看了回复,说说想法吧.....(呜咽中进行....)  
  Ninputer ,测试驱动开发,本身不是给测试人员用的东西,测试人员如果要进行QA测试,就应该通过robot来进行,好的测试工具有很多,rational里面的那个也不错。像Parasoft的.Test,Compuware的DevPartner等,都是给开发人员用的东西,测试人员尽管也可以用,但实际上用处并不特别大,这是程序员本身的责任,当然,如果不放心程序员,他们提交上来代码后,也可以验证一遍。测试人员的义务应该是在于对软件的用户端的输入与输出进行验证,对于内部逻辑处理应该不进行关注。不过,如果公司管理良好,测试人员水平也足够的话,测试人员是应该进行代码复审的,在此类情况,才有机会用用它们。对于测试驱动开发来说,在代码级别上,这个责任是程序员的,而不是测试人员的。NUnit不是一般意义上的单元测试工具,它已经细化到了函数的级别,测试人员应该关注的是模块级别的。  

        蝈蝈商业利益本身与测试驱动开发并无冲突,技术上并无难度,因为代码的完整性及可规约性都是应该事先考虑的,测试驱动开发的成就之处在于,让程序员能够自己有更大程度的控制代码的正确度,相当于提供了两道的代码审核手段,在软件成品的质量上提供了一定的保障。我觉得,它只会减少风险,而不是增加风险。

  kwklover,“我能做的就是保证自己写的代码是正确的”,就是测试驱动开发的目的。所以你的行为是完全正确的。但“test完就注释掉”,这个大可不必,你可以用预编译指令来保留上,让Debug的时候,这些代码有效,RELEASE时无效就行了。   
       

    然后,谈谈看了聊天记录里的想法吧....(继续呜咽,为什么没有机会参加...)
    
    测试驱动开发,是一种开发的方法,但它本身并不是方法学,也没有什么完整的理论体来保证,它更多的是一种技巧。既然是技巧,那么就是有时候有用,有时不一定要用,几千元的项目,开发人员应该能够掌握软件质量的时候,使用TDD,似乎就大材小用了,同时也浪费时间。这与UML一样,在没有复杂逻辑与并发的情况下,采用它描述系统,无疑是自找麻烦。
  TDD很适合于制作产品和大中型项目,因为它们不同于那种小项目,写完了就可以丢掉,公司以后不一定还会做相同的东西的情况。它更多的意义是能够提供一种开发阶段的验证机制。

  测试驱动开发不是为了重构,而是给重构保证了一个可以验证是否正确的方法。因为TDD在使用中,关注的函数的入口与出口,而不是函数的内部逻辑本身,也就是说它只关心正确的输入是出有正确的输出。重构大多数的意义在于改良构架,但往往重构后,开发人员并不清楚,自己重构后导致的后果,这时只要运行一下测试,发现原来的输入与输出集仍然能够通过,就意味着重构的成功,当然这是建立在重构之前,代码运行是通过的基础上的。

  在需求发生变化之后,测试代码肯定是要变化的,测试代码本身应该是对非GUI界面的一种测试。因为测试代码本身是用实例来验证功能,也是另一种视角对需求的描述,这就要求结合敏捷开发中的一些理念了。

  在敏捷开发中,轻装前进是快乐的事情,但这不意味着丢弃一切模型,一切文档,它在是前期工作做了准备的基础下,正如穿越沙漠一样,如果全身都挂满水壶和食品,是不可能穿越的,但是,要是什么都不挂,估计连回头的机会都没有了。。虽然在现实中,完美的平衡是不可以达到的,但这是可以去追求的方向,至于量的把握,只有看个人经验的总结了。

  一般的开发中,都是根据建模的结果来验证代码,代码是否符合模型可以很容易验证,而模型又由谁来验证呢?答案就是TDD了,TDD在这里起到重要但并不关键的作用,它可以对模型进行描述,在写模型的测试代码的时候,因为有仔细地考虑测试的实现,在实际编写中,相当于对模型进行了一次复审,在此就可以找出一些模型上有所矛盾的部分。但为什么说它并不关键?因为TDD是单元性的测试,而不是全局性的测试,要对模型进行验证不但要对细节进行考察,也需要对整体上把握。由于软件开发没有银弹还暂时是一个不争的事实,所以软件的质量保障,还是有许多不可预测的因素在里面,开发人员能够做到的,也暂时仅限于此,毕竟软件开发是团队的事实,从建模到代码撰写都是不可分割的,具体地成功度,就依赖于团队的合作能力了。

  对于TDD的覆盖性问题,应该尽量是做到的GUI与后台代码的分离(无论是winform还是asp.net),即使系统慢上一点,我认为也值得(实际上慢不了多少),对于GUI,本身不应该用TDD来进行,这个可以交给测试人员来单独测试,并通过一些工具来减轻工作负担。利用TDD来覆盖全局代码是一个浪费时间的事情,我很不情愿让GUI的测试占去我大量的时间,实际,如果要TDD来驱动GUI的代码编写,很容易造成资源浪费,毕竟在GUI环境下,用手移动鼠标去点击一下,比写一大堆代码来模拟要快乐得多,更何况大量的机器人工具,已经做得很不错了。

  测试代码写在什么地方比较好呢?有人愿意写在代码类中,有人愿意另外开一个测试类来写,我的感觉是,想怎么样就怎么样吧,这个是没有规定的。就个人而言,我喜欢在一个方法的旁边写一个测试该方法的测试代码,反正使用了预编译后,RELEASE时不编译进去就行了。

  详细说一下我的实践:在类内的内部进行测试时,代码就写在旁边,在类的外部测试时,比如像流程控制,业务规则控制的验证的时候, 尤其是在适当地使用了模式的地方,在模式内的类之间是具有较高的耦合度的,此时如果要测试类的外部调用行为组织而成的功能,那么就在实现此功能的方法旁边写。

  Mock虽然好用,但我觉得它在针对数据库的模拟上并不是一个很好的东西,真正要对数据库进行CURD的时候,测试起来是十分困难的,因为你要更多的时候并不是验证SQL语句本身的错误与否,而是要验证你修改的数据是否正确,虽然这可以通过试图构建可测试的代码来解决一些问题,可惜的是,最终的效果,还是要到数据库之中去验证的。

  对于这种情况,强烈的建议是,不要使用数据库的MOCK,而应该写一个小的测试程序,在数据库外部使用查询指定的记录,通过一个按钮来显示你需要的数据,这样一边测试你的程序的时候,一边查看数据,就比较容易验证了。

  还有,在一些情况下,比较业务规则的验证的时候,使用MOCK也不是一个好主意,因为MOCK对象在使用时,不会有使用的作用,虽然它很轻,但更多的情况下,开发人员还是宁肯重一点,也要保证查看的结果正确,这时,建议采用写直接的实体类,无是DATASET还是DATATABLE,构建越来并不比MOCK的使用更简单时,就可以考虑使用预编译指令,让它们能在DEBUG时生效。正如某位大师所说(是谁不记得了),任何问题都可以引用一个中间层来解决。开发人员,可以写一个代理类并结合预编译指令,让它根据不同的情况,来自动选择究竟是用测试性代码还是真实代码。

  很明显,我大量提到了预编译指令,实际上,充分利用它及NUnit.Framework,可以让你将许多觉得麻烦的东西实现得较为简单,我不否认MOCK的重要性及它的意义,我使用它的情况也有,但相对而言还是比较少的。毕竟现在的MOCK框架不能够满足我们的需求,并且会让我们不得不引用新的数据集,很麻烦,有时候不是删除了就可以解决问题。
  这里不得不提及一个技巧,因为不断地写预编译指令是一件讨厌的事,所以我修改了模板文件。可以在*\Microsoft Visual Studio .NET 2003\VC#\VC#Wizards\CSharpAddClassWiz\Templates\2052\中找到该模板(这里是中文版的Visual Stuido.net2003),我将它改为以下的代码: 

   

using System;
#if DEBUG
using NUnit.Frameworks;
#endif

namespace [!output SAFE_NAMESPACE_NAME]
{
    
/// <summary>
    
/// [!output SAFE_CLASS_NAME] 的摘要说明。
    
/// </summary>

    [TestFixture]
    
public class [!output SAFE_CLASS_NAME]
    
{
        
public [!output SAFE_CLASS_NAME]()
        
{
            
//
            
// TODO: 在此处添加构造函数逻辑
            
//
        }

        
        
TDD
    }

}


  这样,每次添加一个类的时候,自动就有了这些指令,具体的测试代码就可以在#if DEBUG...#endif中进行撰写了。 

    NUnit是一个很好的工具,但毕竟每次点击它运行都不是一件令人愉快的事情,这里,我是采用了dudu介绍的TestDriven.Net工具,在编译时,可以直接调出NUnit,感觉方便了不少,TestDriver.Net的前身就是NUnit Addin,很不错的玩意。

  NUnit这个工具还有一个比较有意义的地方就是,它能够在程序集里面调用指定的方法,不要小看这个,这是一个横切于代码中,进行代码验证的有效手段。比如说,你的DLL中有一个窗体,你得测试它,如果按一般的方式,你可能为选择把当前的编译出当前的程序集后,在主界面中运行,然后再通过点击N次按钮或菜单,找到你想测试的功能,尽管有robot可以使用,但软件开发人员一般不会自己去装这些玩意在开发中用的,就算用了,我也认为软件开发人员就应该专注于开发,不应该受到其它干扰。

  当然,你也可以先把当前的程序编译成exe,然后再调试。不过,我觉得这还是麻烦,个人的经验是,在测试代码中直接写一个把此窗体显示出来,然后进行操作(虽然这个GUI测试是大多数是测试人员的事,但开发人员总得自己测试一下再给测试人员吧?),就显得十分方便了。

  这是工具使用的技巧,很乐意与大家交流以TDD的心得。实际上,我目前的是只做了TD,也就是测试开发,而并没有做到TDD这一步,先写测试代码,感觉时间消耗较大,针对现实与国情,还是TD得了,反正
老外的东西到国内,多半要被糟蹋的,我就糟蹋一下吧

posted @ 2005-01-09 02:33  一根神棍研古今  阅读(1840)  评论(4编辑  收藏  举报
Web Counter