单元测试和测试驱动开发(TDD)杂谈

发表于:2010-9-15 13:33

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:justinchen(cnblogs)    来源:51Testing软件测试网采编

  最近公司要求重新回顾单元测试的实际效果,作为一个开发经理,我个人对单元测试也有很多疑惑。就个人而言,我自己也写过很多单元测试,也鼓励程序员写单元测试,但实际效果似乎不尽如人意。因此,写了这篇短文,想和大家一起探讨。

  1. 背景介绍

  我所在的公司是一家外资软件公司,主要工作是开发一个复杂的在线系统(java based web applicaiton). 该系统的主要特点是:定制化程度比较高,业务逻辑相当复杂。 系统的技术栈是Struts, EJB (JBoss) and Hibernate。我管理的小组一共有10个左右开发人员,6个左右测试人员,平均工作经验在3年以上。

  公司在两年前开始推行单元测试。在开始推行单元测试之前,系统已经正式上线,也就是意味着有海量的没有单元测试的代码。推行之后,应该说投入了相当多的时间,总共覆盖的行数有20k, 其中行覆盖率(line coverage)有55%左右,分支覆盖率(branch coverage)有40%左右。我相信经过这么多尝试,应该说,我带的这个组不是一个单元测试的新手,有资格讨论单元测试的得失。

  2. 实践中的问题和疑惑(开发人员怎么说?)

  我一向主张:一项技术值不值得或者好不好用归根结底是要问实际的使用者和开发者的。作为一个经理,我不倾向于推行一项程序员极力反对的技术,不管这项技术是不是业界的标准或者是评论者的宠儿。一项技术必须要解决实际问题,也就是mark your life easier。所以下面是开发人员的回答。

  2.1 为什么需要单元测试和TDD (Test Driven Development)?

  2.2.1 单元测试可以发现代码缺陷(Defect)么?投入/产出比(Defect count/Effort)是多少?

  只能发现待测单元的缺陷,不能发现单元交互(集成)之间的缺陷。在实践过程中,很少有defect通过单元测试发现。

  基本不能用于发现表现层(JSP, Java scripts, css, UI etc)的代码缺陷,投入/产出比太高。换句话说,相比于单元测试,人工测试(munual testing)可以很大程度得更快更好的发现系统缺陷。

  2.2.2 单元测试可以用来防止Regression Defect么?

  如果我们特地为某个regression defect加了相应的单元测试,那么单元测试在某种程度上可以防止regression defect的再一次出现。但是同样的,单元测试只能防止待测单元中的Regression Defect,而且需要通过猜测来加入相应的测试案例。

  2.2.3 单元测试对设计有帮助么?

  单元测试本身不一定能帮助设计。据说TTD可以帮助设计,实践过程中没有很深的体会。

  2.2.4 你投入了多少时间写单元测试?需要多少时间维护单元测试?

  单元测试:代码 = 2:1,也就是说一行代码需要两行单元测试。也有些人说1:1。

  维护成本基本上决定于单元接口的变化频率:对于一些比较稳定的代码单元,维护成本还可以接受。但对于一些需求变化剧烈的单元,基本上需要重写。在实际实践中,可能的比例为稳定的单元测试:重写的单元测试 = 80%:20%。但是这里有一个悖论:其实我们更希望单元测试可以用于验证(verify)核心单元的正确性,然而这些单元的测试单元确基本上需要重写。这是为什么呢?其中一个可能的原因是:对于一个在线系统(web based application)来说,系统的主要逻辑和用户接口(user interface)绑定过于紧密,所以,用户接口的变化导致从表现层到数据库层的垂直变化。即使业务需求只是加了一个新的属性,但是这个数据将被加入核心的对象当中,所有涉及这个对象的单元测试需要改变。

  2.2.5 单元测试的主要挑战是什么?

  挑战之一:如何在多个测试用例之间共享测试数据。

  公司产品支持一个很复杂的在线向导,由七步组成,每一步可以单独保存然后退出,下次继续编辑。如果你想测试最后一步的API, 你需要准备很多其他页面的数据。因此,需要花很多时间准备测试数据。另外,公司产品还支持相似功能的其他向导。作为一个程序员,我们一直想在多个类似功能的向导API之间共享测试数据。然而,如果待测对象本身有些微变化,所有共享该数据的测试代码全部需要重写。这是一个巨大的维护费用。

  挑战之二:剧烈的需求变化导致维护成本剧增,收益减少。

  正如2.2.4中描述的,一个典型的在线系统(web based application),通常可以分为三层:表现层,主要是用户界面,包括HTML/JSP/CSS/Java Scripts/Ajex等等;业务层,主要是业务逻辑;数据层,存取数据。根据面向对象设计(OOD)的原则,业务层主要由一组领域对象(Business Object/Domain Object)构成。这些领域对象只提供一组数目相对有限的,接口比较清晰的,时间比较稳定的API。对这组API进行单元测试是有必要的,也是有意义的。

  然而,系统还有相当一部分的代码用于调用不同领域对象之间的API, 转变成表现层需要的对象。表现层其他的逻辑还包含大量的代码用于连接不同的页面,以及构建不同的向导。

  正如和绝大多数的系统一样,产品需求的变化是极其剧烈的,可以预测的,不可避免的。在这种情况下,需求变化将导致领域对象API以上的代码(包括绝大多数表现层代码和一部分业务层代码)将发生剧烈变化,与之相应的单元测试代码都需要相应的改变。也就是说,这些代码的单元测试代码的维护成本很好。

  挑战之三:海量的遗留代码(Legacy Codes)

  正如前面描述的,我们是在产品已经上线之后才开始推行单元测试的。因此,大量的遗留代码并不适用于单元测试。换句话说,单元测试必须要在API实现之前予以仔细得考虑。如果API本身没有得到很好的设计,单元测试基本上是不可能的。

  2.2.6 拿什么来衡量单元测试?

  一般来说,业界使用行覆盖率(line coverage)和分支覆盖率(branch coverage)来衡量单元测试的测量。但在实际过程中,我们发现这些衡量标准和我们对单元测试的期望有很大差距:比如说,高覆盖率不见得较少的代码缺陷。高覆盖率也不能防止regression缺陷。高覆盖率也似乎和设计没有直接关联。从另外一个角度说,达到高覆盖率所花费的时间也是相当惊人的。

  从另外一个角度来说,我们希望找到一个方法可以简单直接地衡量单元测试的测量:比如说代码缺陷或regression defect数量。

31/3123>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号