论坛首页 综合技术论坛

[译]优良单元测试的特点

浏览 6416 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2006-10-27  
单元测试类似一种强大的魔法,不恰当的使用则可能浪费大量的时间,给项目带来严重的损害。如果单元测试写得很差、且没有得到很好的应用的话,很容易让你花费大量的时间维护和调试这些测试代码,以至影响到生产代码和整个项目。
我们必须避免发生此类情况。记住,进行单元测试的首要原因是让一切更简单。还好,只要遵循几条简单的指引就能够在项目中避免这些麻烦。
优良的单元测试具有以下的特点:简称为 A-TRIP。
  • 自动性(Automatic)
  • 完备性(Thorough)
  • 可重复性(Repeatable)
  • 独立性(Independent)
  • 专业性(Professional)
  • 下面让我们逐一理解它们的含义。
  • 自动性
  • 单元测试是自动执行的,这里的自动指两个方面:1执行测试,2检查测试结果
    执行测试应该是足够简单的,这样,我们就可以随时随地的进行测试。因此,执行相应测试就应该像在IDE中点击一个按钮,或者在命令行中打一个命令那么简单。一些IDE甚至会在后台连续的进行测试。
    维护这个环境很关键,不要引入哪些会破坏自动测试模型,需要手工进行干预的单元测试。如果测试需要一些环境(网络,数据库,等等),那就把它做为测试的一部分。Mock对象可以有效的隔离对外部的依赖。
    运行测试的不光你一个人,还应该有一台机器对所有提交的代码持续的运行所有的测试。这种自动、无人职守的检查的作用就像一个定位杆,这种安全机制保证所有提交的东西不会破坏任何测试。理想情况下并不必须这样子,因为你可以依靠每一个开发人员都会自行运行所有必需的测试。回到现实,可能某个家伙再某个遥远的地方并没有执行必需的测试。也许在他的机器上有一些代码能够保证一切没问题,可他们却没有提交代码。这样虽然在本地可以执行,其他地方就会出问题。
    最后,自动性的含义还有测试必须能够自行判断成功还是失败。让一个人(你或者其他倒霉蛋)通过检查测试产生的数据结果来判断代码的正确性,这是让项目失败不错的方式。测试的自检查是一致回归的重要特性。人类并不擅长这种重复性的工作,另外,在项目里,我们还有很多更重要的事情去做。
    测试的自动执行和自动检查是非常关键的,这意味着你不用花太多的心思再这上面,它已经成为了项目的一部分。测试是项目安全保护网的重要组成部分。它在你掉下来时接住你,且不会挡道,这样你就可以集中精力走"钢丝"了。
  • 彻底性
  • 良好的测试应该是彻底的,所有可能会出错的地方都应该测到。如何能够做到这一点呢?一种极端情况,测试每一行代码、每一个分支、每一个抛出的异常,诸如此类;另一个极端,只测试最可能的情况:边界条件、数据丢失、数据格式无效,等等。这就需要根据项目情况进行区分了。
    如果追求较高的测试覆盖度,那就需要寻求代码覆盖工具来帮忙了(例如:免费的nounit,quilt,商用的Clover),用这些工具可以知道到底有多少代码是被测试覆盖的。
    有一个事实需要注意,Bug在代码中的分布情况是不均匀的,而是喜欢聚集在有问题的地方(很多昆虫都这样,例如:苍蝇)。
    这种现象引出了一段非常著名的呼声“别打补丁,重写”。通常,从头重写一段有一堆问题的代码的代价和痛苦程度要低得多。当然,有了单元测试,从头重写代码也安全多了,单元测试可以保证新代码能够按照预定的执行。
  • 可重复性
  • 测试用例之间是独立的同时,也应该是独立于环境的。目标就是保持每个测试能够以任意顺序、不断的重复执行,且得到同样的结果。这意味着测试不能依赖于不可控的任何外部环境。
    使用Mock对象来隔离测试,保证测试不依赖于外部环境。如果必须依赖一些条件(例如:数据库),那就要保证这个条件不受其他开发人员的干扰。每个开发人员都应该有自己的sandbox,不同的数据库实例啦,web服务器啦。
    没有可重复性保证,可能会在最糟糕的时侯遇到一些让你奇怪的问题,更糟糕的是,这些奇怪的问题通常都是虚幻的,并不是一个真正的bug,只是测试自身的问题。真不值得为这些虚幻的问题浪费时间。
    每个测试用例,每次执行都应该得到同样的结果。如果测试结果是失败,那就说明是在代码里面存在bug,而不是有其他问题引起的错误。
  • 独立性
  • 测试代码必须保持精简、整洁,这就要求测试是专注的、与环境和其他测试隔离的(要记住,别的开发人员可能同时在运行这些测试)。
    在书写测试时,保证每个测试只做一件事情。这不是说在一个测试方法里只写一个assert,而是说在一个测试方法里应该只专注于一个、或者几个共同提供某些功能的方法产品代码。有些时候一个测试方法只能够测试了一个复杂的产品代码方法的一部分,就需要一套测试方法来完整的测试产品代码的方法了。
    理想情况下,我们希望在测试代码和潜在的bug之间建立起关联。也就是说,当一个测试方法fail的时候,能够定位到对应代码中的bug。独立性也意味着test之间没有相互依赖。每一个test都是可以独立运行的,而不需要必须在其他test之后才能运行。每一个test应该是一个孤岛。
  • 专业性
  • 为单元测试所写的代码是货真价实的,甚至有些人会争辩说,比提交个客户的源代码还要货真价实。这意味着,测试代码的书写和维护应该保持和生产代码一样的专业水准。产品代码中必须遵循的常用的设计准则,如:数据封装、DRY原则、高内聚、低耦合等等,在测试代码中也一样要遵守。
    测试代码很容易就会写成linear的样式,代码里面充次着同样的内容,不断的重复同样的代码,却鲜见方法和对象。这样很糟糕,测试代码必须和生产代码按同等对待来书写。这就要把哪些常用的、重复部分的代码抽取出来成为一个方法,这样就可以在多处调用。
    这样就会慢慢积累出一些测试的方法,可以封装在一个类里面了。别争论什么,就算这个类只是用来测试的,就创建一个好了。这样做不进没问题,而且是应该得到鼓励的:测试代码是货真价实的代码。某些情况下,我们也许需要创建一个更大的框架,或者创建一个数据驱动的测试工具。
    不要浪费时间测试那些不必要的方面,我们不是为了测试而创建test。测试的完整性要求测试方法的每一个可能产生bug的方面。如果没有产生bug的可能,就不要做测试。比如get set方法,就没必要做测试了。但如果这些get set方法中包含了业务逻辑,哪就有必要进行测试了。
    最后,测试代码应该和生产代码是同一规模量级的。是的,你绝对没有看错。如果产品代码有20000行,那么测试代码至少也应该是20000行,甚至更多。测试代码的量也很大,就必须保持整洁、精简,有良好的设计,结构良好的,同生产代码一样的专业。
       发表时间:2006-11-08  
    翻译的不好,见谅了。
    0 请登录后投票
       发表时间:2006-11-08  
    翻得挺好的,不仔细看还以为是原文呢
    0 请登录后投票
       发表时间:2006-11-08  
    谢谢!
    这部分多是泛泛讲讲,对具体怎么做指导意义不大,但如果测试方面工作做的较多,算是一个总结提炼,还是不错的。原书名叫做:<pragmatic unit testing in java with junit>,因为是英文原版的,不便贴再这里,花了点时间把觉得还不错的部分翻译出来,跟大家分享下。
    0 请登录后投票
    论坛首页 综合技术版

    跳转论坛:
    Global site tag (gtag.js) - Google Analytics