敏捷技术实践中TDD引入时机的辩证思考
TDD(全称Test Driven Development)测试驱动开发,是一种软件开发的流程,其由敏捷的“极限编程”引入。其开发过程是从功能需求的测试用例开始,先添加一个测试用例,然后运行所有的测试用例看看有没有问题,再实现测试用例所要测试的功能,然后再运行测试用例,查看是否有case失败,然后重构代码,再重复以上步骤。
TDD作为质量保证的核心手段,显得尤为重要。能否严格执行TDD进行研发也是衡量团队转型成熟度的重要标志之一。TDD在研发过程中的重要性不言而喻,但是何时进行TDD实践,在敏捷实践过程中有一些辩证性的思考。
总结了一些前人分享的TDD的好处:
1.TDD根据客户需求编写测试用例,对功能的过程和接口都进行了设计,而且这种从使用者角度对代码进行的设计通常更符合后期开发的需求。因为关注用户反馈,可以及时响应需求变更,同时因为从使用者角度出发的简单设计,也可以更快地适应变化
2.出于易测试和测试独立性的要求,将促使我们实现松耦合的设计,并更多地依赖于接口而非具体的类,提高系统的可扩展性和抗变性。而且TDD明显地缩短了设计决策的反馈循环,使我们几秒或几分钟之内就能获得反馈。
3.将测试工作提到编码之前,并频繁地运行所有测试,可以尽量地避免和尽早地发现错误,极大地降低了后续测试及修复的成本,提高了代码的质量。在测试的保护下,不断重构代码,以消除重复设计,优化设计结构,提高了代码的重用性,从而提高了软件产品的质量
4.TDD提供了持续的回归测试,使我们拥有重构的勇气,因为代码的改动导致系统其他部分产生任何异常,测试都会立刻通知我们。完整的测试会帮助我们持续地跟踪整个系统的状态,因此我们就不需要担心会产生什么不可预知的副作用了。
5.TDD所产生的单元测试代码就是最完美的开发者文档,它们展示了所有的API该如何使用以及是如何运作的,而且它们与工作代码保持同步,永远是最新的。
但是确实如此吗?TDD所带来的好处被过度的夸大。
1.有相当多的代码不只是适用于单元测试,即协调/组织类型的代码,它们需要一些依赖关系,通过调用几种方法,把代码从这里移植到那里,由于其沉重的mocks和stubs 的使用,这种编写测试的代码比代码本身要复杂的多。
2.软件开发的最大困难在于用户需求的不确定性,根据用户需求编写测试用例,除非针对一些很简单或者很明确的需求,否则你的测试用例根本不能真正反映用户的需求。TDD从一定程度上提前做了一个假设,那就是“需求可以在实现之前明确下来”,这是不现实的。在实际的软件开发中,用户需求和外部环境的不稳定性会导致软件需求难以把握。只有让用户看到了运行的软件,用户才能驱使软件朝着他想要的方向前进,通常需求和设计都会不断调整。
3.“在没有测试用例失败之前,不要写任何一行代码”这种极端的方式,会导致我们的设计不是迎合用户需求,而是迎合测试用例,而如前面所说,测试用例很难在一开始就符合用户需求。我们完全可以在完成系统功能的同时,完成单元测试,然后通过重构的方式来改善既有代码的质量。
4.频繁地运行所有测试,可以尽量地避免和尽早地发现错误,极大地降低了后续测试及修复的成本,提高了代码的质量。”但如果把这些放在代码之前,那么我们如何保证自己的测试用例写得恰当,一旦需求变更,或者对需求理解有误,那么修改测试用例,意味着我们的编码全部废弃了,不要小看这个成本,很可能会出现这种情况:我们花成倍的时间去维护我们的测试用例,却不能如期交付我们的软件。
5.TDD强调“先理解清楚需求,把它转化为测试用例,然后再来实现和重构“, 它在一定程度上不可避免地面临和瀑布模型相同的问题:“忽略了软件需求的产生是一个在实际运行中不断调整探索完善的过程。”
那么鉴于此,TDD应该如何开展呢?
以某金融系统某软件项目为例,我们针对该项目做了需求分解,从技术实现上,分为http基础网络层,第三方token刷新模块,视频设备增删改查操作模块,视频流读取播放模块,用户界面数据交互模块;
我们针对变化少,功能稳定的基础层模块,即http基础网络层,网络摄像头设备操作层做了TDD的案例编写;适当的放弃用户界面数据交互模块的TDD的编写;在项目中期和后期需求收敛,功能稳定以后,补充了部分和核心业务交互模块的测试用例的增加,取得了较好的效果;
至此,通过理论论证和实践,我们得出的基本结论如下:
1.TDD依然是研发流程中不可或缺的一环
2.针对需求中变化较少,基础层,框架层的逻辑尽早的提供TDD案例十分有必要
3.针对需求不确定,需求频繁变更的接口和复杂逻辑,尽早的提供TDD接口会延误开发进程,TDD的执行需要适度,在项目中前期针对易变的接口进行TDD导入值得商榷,需要谨慎;
网友评论