测试驱动开发实践

发表于:2015-11-19 08:42

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

 作者:偶行势箭    来源:51Testing软件测试网采编

  一.前言
  不知道大家有没听过“测试先行的开发”这一说法,作为一种开发实践,在过去进行开发时,一般是先开发用户界面或者是类,然后再在此基础上编写测试。
  但在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等。
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

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

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

沪ICP备05003035号

沪公网安备 31010102002173号