测试驱动开发实践 - Test-Driven Development(转)

一.前言

不知道大家有没听过“测试先行的开发”这一说法,作为一种开发实践,在过去进行开发时,一般是先开发用户界面或者是类,然后再在此基础上编写测试。

但在TDD中,首先是进行测试用例的编写,然后再进行类或者用户界面的开发。由于要先开发测试用例,那么开发人员就必须清楚测试的目的,所测功能模块的业务逻辑以及需要测试的场景。

这样TDD确保了项目的代码与所需的业务是匹配的,并且在日后的开发工作中也能确保之前所做的功能的可测试性。

很多同学问TDD是使用那种编程语言,或者是某种技术,这里需要明确的是,TDD并不是某种技术,而是一种项目实践。

导语:

传统开发模式与TDD开发模式的区别在哪里?TDD开发的困难之处和优点是什么?TDD具体开发过程中又需要用到哪些技术知识点?且看本文作者通过实例来为你阐述TDD的开发流程,让你对TDD有一个大致的了解。

二.传统开发模式与TDD开发模式

1. 传统开发模式流程:

项目代码开发 -> 编写测试用例 –> 运行测试用例 -> 修复代码BUG

2. TDD开发模式流程

编写测试用例 -> 运行测试用例 –> 编写项目代码 -> 运行测试用例 -> 重构代码

三.TDD入门难题

说到写测试用例,一般的同学都觉得没啥问题,就根据已有的代码,顺着葫芦摸瓜的就把该测试的方法都照着“套”一遍,测试有哪些结果也得顺着项目的代码来。

至于这测试代码能不能测出项目的问题,那是另外的问题,关键是测试代码要Pass,那我的工作才能算完。

但是要在还没项目代码之前写测试用例,那就是等于我要凭空想象那些个抽象又晦涩难懂的功能点,还得要在心中勾勒出它们的轮廓以及细节,这不等于让我自己画个饼来充饥么?问题是我连这饼应该是啥样子都还没个谱,这是何等的悲凉啊,要是“蓝胖子”在我身边就好了~

四.TDD的优点

1. 保证代码质量,鼓励开发人员仅编写满足需求的代码。

“李雷”同学号称马虎王,经常各种物品丢失,比如女友(对象)丢失;借用物品(引用)丢失;使用“IO自来水”之后不关阀门;去仓库(Database)取货,因为忘记某些物品,不得不频繁往返等等。

“韩梅梅”同学功力深厚,精心打造出了一段瑞士军刀般的代码,耗时5天(其实此功能客户无扩展需求仅要求1天时间完成)。

而在TDD实践中,我们需要注重代码质量,并编写刚好适量的代码。

2. 保证代码与业务需求的一致性

一般来讲程序员都愿意把功能完美的体现在代码上,可有时候天不随人意,心里免不得担忧,我这代码能满足业务需求么?但在TDD中,首先是进行测试用例的编写,然后再进行类或者用户界面的开发。由于要先开发测试用例,那么开发人员就必须清楚测试的目的,这样TDD确保了项目的代码与所需的业务是匹配的。

3. 创建简明有针对性的接口

一日,“李雷”接到“韩梅梅”发来的为某个功能准备的闯关宝典和核心步骤(类库与接口)。可读了3000遍还是没有能理解,一方面是“韩梅梅”采用了古代文言文与现代拉丁语的混搭来书写核心步骤,另一方面“韩梅梅”的“韩”式1到1000000的命名规则让“李雷”在读了30秒核心步骤后,已经不知道第几条是第几条,。

在TDD实践中,我们要注重创建有意义的、简明的接口,因为这一点在与他人合作中尤其重要。

4. 与用户沟通,明确需求

在开发代码的过程中,我们总会有遇到不太明确的需求点,这个时候和需求人员沟通那是必不可少的,了解了功能的输入和输出才能保证完美的完成任务。在沟通的过程中也加深了与客户的信任和默契度,不知不觉中还能提高EQ,一举两得。

5. 回归测试,确保新的更改不影响现有功能

在“韩梅梅”同学开发某个功能3个月后,“李雷”接到上级指示,客户要扩展该功能,但是原有功能保持不变。在苦心操劳了之后,“李雷”同学光荣的完成了任务,正准备接受大家赞誉时,“韩梅梅”跳出来向大家诉苦,那就是“李雷”为了做扩展功能把她之前做的功能给弄坏了,当时“李雷”那个心啊,拔凉拔凉的!

TDD的开发中加入了回归测试,这样就确保了之前的功能的正确与完整性,减少不必要的问题。

6. 提升系统的开放性和扩展性

一直以来我们做事都要讲先后顺序,软件开发也有着类似的工序。“李雷”和“韩梅梅”被一起“充军”到某紧急功能模块上,并且“李雷”要等“韩梅梅”完成她的功能模块才能开始自己的模块。为了解决这个问题,项目组决定使用某些技术来解除他们的依赖关系,比如使用到IOC以及一些设计模式,让他们能够同时开发,之后再将两人的功能模块组装到一起。

五.TDD开发中需要使用到的技术知识点单元测试、依赖注入框架和模拟对象

1. TDD的工作流

TDD的工作流经常被描述为“红灯 -> 绿灯 -> 重构”:首先以一个未能通过的测试开始,随后编写足以通过该测试的代码,然后再重构代码。当然我们都不愿意看到不能通过的测试CASE,当你再继续编写项目代码,让原本不能通过的测试CASE通过的时候,你会感觉心里有一丝丝的惬意,然后再将代码优化重构,瞬间又有了些成就感。抿一口水,工作就这么快乐的完成了。

2. 伪对象、依赖注入框(DI/IOC)与模拟框架

就最简单的实践来说,比较常见的三层架构,UI层去调用业务逻辑层,业务逻辑层去调用数据持久层。

“韩梅梅”做业务层的代码,“李雷”做数据层的代码,于是乎“韩梅梅”变成了“黄世仁”,“李雷”就成了“杨白劳”,其中辛酸只有“李雷”知道!为了改变命运,“李雷”决定做个“假”的数据层对象(模拟对象)给“韩梅梅”用着,省的她每天都在那催命。

伪对象是对代替外部资源的简单模拟,它通常会在调用一个方法时为该方法返回预定义响应,但通常不会根据输入参数而改变响应。

于是乎“李雷”欢乐的开始了他的计划,把“韩梅梅”所需要的功能点都用接口来实现(interface),然后把这接口的方法在单独的一个模拟类里面都只写了个简单的壳,里面的各种返回值都写成“韩梅梅”想要的数据样例,最后语重心长的对“韩梅梅”说:“东西拿走喜儿给我留下…”,“韩梅梅”当然是欢快的蹦到了自己的座位上。

控制反转是对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。所以,控制反转是,关于一个对象如何获取他所依赖的对象的引用,这个责任的反转

“韩梅梅”拿到李雷给的伪对象后,径直就用了起来,在所有需要伪对象的类里面都直接用了万能的“New”关键字来实例化这个伪对象,这招兼顾了简单与实惠,广大程序员爱好者都爱这么干。

但是 “韩梅梅”后来慢慢意识到不太对,我有许多地方都用用到NEW字,那不是以后“李雷”完成了他所谓的真的对象以后,我还必须得改我的代码,把我之前放进去的伪对象给替换为真正的对象?我这不是自己给自己找茬么。

“韩梅梅”赶紧找到带着黑框身背双肩包的师兄,细说了当前的苦衷。黑框师兄那舍得是师妹这么忧愁,赶紧拿出杀手锏“控制反转”中的一招“依赖注入”,让使用类中仅保留被调用对象的接口,然后动态的注入实例给这接口,这样子只要实现了这个接口的类都可以被任意替换使用,并且这个注入的动作一般是由某个框架来实现的,比如Autofac,、Unity或者Ninject等等。

这下子“韩梅梅”心里踏实了,管你“李雷,张雷,王雷”写什么伪对象或者真的对象,只要你的对象实现了指定的接口,我都能使用,而且我还不用自己去手动创建这个对象,省心又省时。

模拟框架是一系列用于快速创建伪对象的API,它能减少重复的代码,提高编码效率,比较常用的为Rhino, NSubstitute, Moq等。

“李雷”和“韩梅梅”就这么和谐的合作,但是随着任务的增多,发现问题来咯:

1)       “李雷”要手工做很多的伪对象给“韩梅梅”,任务繁重。

2)       每个对象的内部需求是不一样的,“李雷”发现要用一种通用的格式来创建这些伪对象几乎是不可能的。

3)       很多伪对象又依赖于其他的伪对象,这样子简直就是要让崩溃。

4)       很多为对象内部有状态需要保存,手动来写代码很难去维护这些状态标示。

“李雷”好不容易通过创建伪对象来摆脱“韩梅梅”的每日请安,这又掉进了创建无数伪对象的漩涡之中。于是“李雷”横渡远洋,爬山涉水,期望能找到一盏明灯解决这些问题,终于功夫不负有心人,“李雷”找到了神兵利器去解决这个问题,那就是模拟框架。

模拟框架帮助“李雷”快速的创建了各种“韩梅梅”需要的模拟对象,以及各种所需的API,弹指一挥间,李雷用这神兵利器已经杀敌无数,嘴角不由得上扬了一番!

3. 重构代码

但是问题总归还是有得,她发现自己有些功能虽然测试通过,但是代码写的不好,经常被黑眼圈师兄批评,说她的代码质量不高。

所以她必须尽可能在刚测试通过之后就尽可能的优化代码,一来是少挨骂;二来也是提高自己编码水平的一个机会,查漏补缺;三是贵人多忘事,何况是“我这等如花似玉的姑娘!”,如果不及早优化,恐怕以后很难有时间再来弄了。

因为已经有了测试代码,所以重构代码那也是很有保障的事情,如果我改错东西了,那么我写的测试用例肯定不能通过,这样子也能让我信心满满的去把这些个有臭味的代码大卸八块了。

六.工序流程

下面我们来看看“韩梅梅”和“李雷”他们的工作步骤:

1. 首先,韩梅梅和李雷分析了他们各自的业务,然后韩梅梅写出了她需要测试用例,里面尝试使用“李雷”将要提供的方法,并通过此方法获取数据。当然这些代码第一次是测试不通过的,因为里面需要的实现类还没有写。这里我们使用到Moq这样一个模拟框架。

测试用例的运行结果,大家也是知道的,两个字“悲催”!

2. 然后, “李雷”那边开始了数据持久层接口的编写(IProductRepository),“韩梅梅”拿到李雷提供的接口后,完成了业务逻辑层(ProductService)的代码编写,完毕之后大吐一口气:“小伙子终于给力了一次!”。

A.  “李雷”的代码如下,实际上“李雷”只是提供了接口(interface)给“韩梅梅”,他还并没有开始编写具体的实现类,但是韩梅梅已经可以通过该接口来工作了。

B. “韩梅梅”的服务类代码如下,她获取到“李雷”提供的数据持久层的接口后就开始欢快的编写代码,一切是那么的行云流水啊:

3. 接下来“韩梅梅”添加了各种需要的引用,再次运行起了测试用例,这次顺利的PASS了,心里那个激动,没的说!

4. 工作快要接近尾声,不过眼镜师兄提醒过“广大程序猿应该有高度的思想觉悟,不遗余力的提高代码质量”,为了达成这一目标,“韩梅梅”又开始了上跳下窜的“大家来找茬”。

她发现里面有段代码写的不好,循环太多,也不够整洁,她想优化下代码,又怕把写好逻辑弄坏了,不过现在有了测试用例,她不会再怕有这个问题,改错代码,测试用例自然也就无法通过。

  

再运行下测试用例,依然通过,此次代码优化完毕,如果还有新的问题可以在依葫芦画瓢的继续优化。

5. 与此同时,“李雷”那边的数据持久层代码也差不多写好了,大家总得需要把代码合起来作“集成测试”,这个时候就要用到IOC框架来把“李雷”编写的数据层实例注入到业务逻辑层,注入实例使用的是Autofac这个IOC框架,我们这里使用构造函数注入,关于注入框架的更多信息,请读者G….gle。

至此,“韩梅梅”与“李雷”各自的工作都完成了,大家也不在互相说啥,各自都优化了各自的功能代码,快乐的工作继续进行着,我们的TDD讲解也到此结束!

参考文献:

  1. Test Driven Development: By Example – Kent beck
  2. Refactoring: Improving the Design of Existing Code – Kent beck
  3. The Art of Unit Testing: With examples in .NET – Roy Osherove
  4. Professional test driven development with C# - James Bender, Jeff McWherter

本人最近开始学习TDD,藉此提升自己的能力。在学习过程会有一些心得体会,于是便会写一些博客来记录这些想法,有兴趣的朋友可以和我一起交流学习。

QQ群: 32745894,欢迎大家加入讨论!

http://www.cnblogs.com/zhq3051/p/4596049.html

时间: 2024-10-02 02:37:15

测试驱动开发实践 - Test-Driven Development(转)的相关文章

测试驱动开发实践 - Test-Driven Development

一.前言 不知道大家有没听过“测试先行的开发”这一说法,作为一种开发实践,在过去进行开发时,一般是先开发用户界面或者是类,然后再在此基础上编写测试. 但在TDD中,首先是进行测试用例的编写,然后再进行类或者用户界面的开发.由于要先开发测试用例,那么开发人员就必须清楚测试的目的,所测功能模块的业务逻辑以及需要测试的场景. 这样TDD确保了项目的代码与所需的业务是匹配的,并且在日后的开发工作中也能确保之前所做的功能的可测试性. 很多同学问TDD是使用那种编程语言,或者是某种技术,这里需要明确的是,T

测试驱动开发实践

总是以为自己了解了测试驱动开发,其实做起来和了解根本不是一回事.原来觉得代码清晰得很,后来试验了一下才知道那是自己的错觉.这次,让我们抛却Eclipse的自动补全功能,来一场真正的测试驱动开发吧. 项目描述:这是一个很简单的项目,目标是扫描磁盘上所有特定格式的文件,将其路径存储下来,通过程序可以快捷搜索到文件路径并自动定位到该文件. 用户故事(简单点写了): 1.              扫描磁盘,将目录下的所有文件列出来,将特定格式的文件信息存储到磁盘. 2.              所有

测试驱动开发(Test-Driven Development)

1.背景 一个高效的软件开发过程对软件开发人员来说是至关重要的,决定着开发是痛苦的挣扎,还是不断进步的喜悦.国人对软件蓝领的不屑,对繁琐冗长的传统开发过程的不耐,使大多数开发人员无所适从.最近兴起的一些软件开发过程相关的技术,提供一些比较高效.实用的软件过程开发方法.其中比较基础.关键的一个技术就是测试驱动开发(Test-Driven Development).虽然TDD光大于极限编程,但测试驱动开发完全可以单独应用.下面就从开发人员使用的角度进行介绍,使开发人员用最少的代价尽快理解.掌握.应用

测试驱动开发(TDD)

在编写程序之前,先确定程序中的变量.控件等元素允许的值.若在编写程序时,变量.控件中的值与事先确定的值不相符,就说明程序的某处有bug,这种测试方法就是TDD(Test Driven Development,测试驱动开发).TDD与OpenGL ES一样,只是一套标准或一套API.Android SDK中提供了一套测试框架(JUnit),可用于对Android应用程序进行TDD测试.测试框架的特性如下: Android的测试框架基于JUnit.可在无需调用Android SDK API的情况下测

《测试驱动开发》之介绍

本书为<Test-Driven Development By Example>by Kent Beck[America].由人民出版社出版,孙方注释. 以下为学艺不精的我综合英文原文和注释对这本书的翻译.小部分参考了孙平平等人的译本. 介绍 早在星期五,老板到Ward这儿向他介绍一个WyCash的潜在客户,Peter.WyCash是公司正在销售的债券组合管理系统.Peter说,”我对我看到的功能印象深很刻.不管怎么说,我注意到你只办理美元计价的债券.我正在创办一种新的债券,我的战略需要办理不同

TDD(测试驱动开发)培训录

2014年我一直从事在敏捷实践咨询项目,这也是我颇有收获的一年,特别是咨询项目的每一点改变,不管是代码质量的提高,还是自组织团队的建设,都能让我们感到欣慰.涉及人的问题都是复杂问题,改变人,改变一个组织是个更复杂问题,这里可能涉及很多的非技术,非能力问题. 在2014年12月我在某企业内部推行TDD(测试驱动开发)培训,一共分4个课时完成一个特定需求的例子,看着大家一步一步的加深对TDD的理解,直到2014-12-31,也是2014的最后一天下午培训完TDD课程,经过一系列的总结过后,某参与人员

测试驱动开发与Python

最近在看一本书<Test-Driven Development with Python>,里面非常详细的介绍了如何一步一步通过测试驱动开发(TDD)的方式开发Web项目.刚好这本书中使用了我之前所了解的一些技术,Django.selenium.unittest等.所以,读下来受益匪浅. 我相信不少开发都写单元测试,不过,一般是先写功能代码,然后,再写单元测试用例,在编写单元测试用例的过程中,可能需要调整功能代码,从而使单元测试用例通过.但是TDD就特别要求先写测试用例,后写实现代码.这一开始确

浅谈测试驱动开发(TDD)

1. 优势 TDD的基本思路就是通过测试来推动整个开发的进行.而测试驱动开发技术并不只是单纯的测试工作. 需求向来就是软件开发过程中感觉最不好明确描述.易变的东西.这里说的需求不只是指用户的需求,还包括对代码的使用需求.很多开发人员最害怕的就是后期还要修改某个类或者函数的接口进行修改或者扩展,为什么会发生这样的事情就是因为这部分代码的使用需求没有很好的描述.测试驱动开发就是通过编写测试用例,先考虑代码的使用需求(包括功能.过程.接口等),而且这个描述是无二义的,可执行验证的. 通过编写这部分代码

测试驱动开发(TDD)及测试框架Mocha.js入门学习

组里马上要转变开发模式,由传统的开发模式(Developer开发,QA测试),转变为尝试TDD(Test-driven development,测试驱动开发)的开发模型.由此将不存在QA的角色,或者仅存很少的QA用于系统模块间的集成测试. 因此代码的测试与开发都将由开发者(Developer)来保证. 这就需要借助优秀测试框架的帮助,尤其是支持TDD开发模式的自动化测试框架更为重要,因为我使用的编程是语言是Node.js,那么广泛使用的Mocha.js将成为我的首选. 在团队转型过程中,很多事情