敏捷质疑:TDD

发表于:2008-7-15 11:52

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

 作者:切尔斯基    来源:blogjava

通常单元测试有两个公认的约束需要满足:

        1、快

        2、隔离依赖.

        重申一遍结论就是: 在满足单元测试的快和隔离依赖的前提下,

        1、优先选择基于状态的黑盒测试(可使用手写stub或mock退化的stub)

        2、除非交互和行为本身就是需求(可使用mock对象的全部特性)

Q: 怎么测 private 函数?

A: 把它变成 public 的.

        我是认真的. 如果发现 private 函数无法简单的通过某个public函数的测试来覆盖而需要专门的测试, 意味着你的单元可能承担了太多的职责, 应该拆分到一个单独的单元中, 并开放为 public 函数.

        如果使用 C++, 在测试环境中 #define private public.

        如果使用 g++, 在测试环境中加入 -fno-access-control.

Q: 类似 private, 一些意图实现良好设计的语言特性, 如 static, sealed, final, 非虚函数等, 却总是给代码的易测试性带来麻烦, 该如何取舍?

A: 没什么好办法. 这些语言特性和测试的目的是相同的, 都是为提高代码质量, 减少出错的可能, 虽殊途同归, 但却互相限制, 效果也不一样.

        我认为工业界是时候严肃认真的考虑测试环境了, 最好在语言中内建对测试的支持, 一些为产品环境设计的语言特性, 应该在测试环境中关闭, 而在产品环境中生效. 其实之前很多编译器都支持 Release 和 Debug 两种环境, 也是从代码质量的方面考虑的. 现在毫无疑问证实单元测试比 Debug 更有效, 是时候与时俱进增加对 Test 的支持而逐渐罢黜对 Debug 的支持.

        在语言本身增加对测试的支持之前, 我们不得不想办法在测试环境中绕过语言特性的限制, 尤其对遗留系统, 代码已经存在的情况. 比如对于 C++ 中的 static 函数, 可以将整个被测单元 #include, 或者 #define static 为空. 宏代表了一层间接, 在测试环境中, 这层间接是至关重要的. 其它方法可参考 <<Working Effectively with Legacy Code>>, <<假冒的艺术>>中的介绍.

Q: 刚才提到了要支持"测试"而不是"Debug", 测试和Debug难道有什么矛盾吗?

A: 有. 如果你发现不得不 Debug, 就是测试粒度太粗, 步子迈的太大, 产品代码过长等导致的, 甚至可能你卷入了过多的单元而破坏了测试的隔离性. Debug还是代码逻辑不清, 行为难以断言的表现.  用测试帮你定位错误.

Q: 我知道为遗留系统增加新特性是要先写测试保证系统原来的行为, 可遗留代码很庞大, 我甚至都不知道系统目前的行为, 怎么办?

A: 特征测试: 保持代码行为的测试, 获取当前运行结果, 来填充测试, 以获取系统目前行为. 其实测试可以分为两类: 试图说明想要实现的目标, 或者试图保持代码中既有的行为; 在特性实现后, 前者会转化为后者. 详细信息请参见<<Working Effectively with Legacy Code>>

Q: 前面经常说到 C++ 或其它面向对象语言, 却没有提到 C, 那么过程式语言中如何应用 TDD ? 有什么不一样?

A: 基本一样, 并且在过程式语言中应用 TDD, 可能会导出面向对象风格的设计. 比如如果直接调用某个函数, 那么不得不通过编译时替换或链接时替换来接入假的实现. 这样其实比较麻烦, 因此可能会促使你选用函数指针 ,以便方便的在测试环境中进行替换. 随着时间的推移, 你会发现一组组概念相关的函数指针出现了, 那么把它们和它们操作的数据绑定在一起, 定义一个 struct, 就形成了一种对象风格. 当然这反而可能会令你的代码更复杂, 这需要在实践中取舍.

        也有可能在过程式语言中你觉得 TDD 对设计的促进不大, 而且测试用例也比较枯燥, 就是测个分支, 返回值什么的. 是的, 逻辑就隐藏在分支和返回值中, 如果习惯了过程式思维并不打算改变, TDD 对设计的影响则更多的体现在依赖管理上, 如头文件和编译单元的职责划分. 如果把不同职责的函数混在一个编译单元里面, 则很难实施链接替换等手段, 除非你选择一个类似 mockcpp 的框架, 不需要链接替换.

Q: 如果使用 TDD, 那么测试人员怎么安排? 是不是一开始就要进入项目组? 可那时还没有产品代码,测什么?

A: 是, 是一开始就要进入项目组, 可不是因为 TDD.  是, 测试人员是一开始没什么可测的, 可不代表就没活干.

        TDD是一种开发方法, 是开发人员参与的活动. 其效果是以可执行的形式文档化你的需求, 迫使你分清职责隔离依赖以驱动你的设计, 编织安全网以扼杀Bug在摇篮状态防止逃逸. 可传统测试人员的活动是试图找到已经逃逸的Bug. 这两种活动都是必要的, 而且毫不冲突, 互为补充.

        那么测试人员在新的特性还没开发完成之前做什么呢? 除了提前写测试用例, 无论是自动化的还是非自动化的, 而需要测试人员参加的一项重要活动, 就是参与特性验收条件的制定. 之前经常发生开发人员按照自己的理解去编码, 测试人员按照自己的理解去测试, 直到开发完成, 测试过程中才发现理解的不一致, 开始产生争执并阻塞等待业务分析人员(如果幸运的话)或者行政主管(如果开发过程混乱的话)的仲裁. 解决办法就是就在开始开发新特性前的一刹那, 由业务分析人员, 测试人员, 开发人员进行一次讨论, 就验收条件达成一致并形成记录, 然后测试人员和开发人员分头去写测试和实现.

Q: 之前会有一个阶段, 就是一组相关的特性开发完成后, 测试人员接手测试, 几轮Bug修复过去后, 产品基本稳定就可以发布了. 现在测试人员提前介入到每个迭代中, 针对单个特性进行测试, 那如何保证产品集成起来的质量?

A: 跟以前一样, 该有那么个集成测试阶段还得有那么个集成测试阶段, 取决于产品当时的质量状态. 并不是说有了迭代级别, 单个特性级别的测试就不需要发布级别的集成测试了, 两者没有任何矛盾.

Q: 那么测试人员提前进入迭代有什么好处?

A: 尽早发现问题, 降低修复错误的成本. 有几种手段, 一是前面提到与业务人员和开发者一起讨论验收条件, 这样就能防止理解偏差而导致的返工. 二是开发完成立即测试, 发现问题立即反馈, 这样开发人员对代码依然印象深刻,能快速定位和修复错误. 这样流入最后集成测试阶段的Bug就会少, 会缩短最后的集成测试时间, 保证产品更平稳的发布.

Q: 有时候后续的特性会影响前面的特性, 那么迭代过程中测试人员只测单个特性, 怎么保证以前的特性依然工作?

A: 几个手段. 测试尽量自动化, 以便能够持续集成. 再就是做好依赖管理, 每当一个新特性完成, 就应该能够发现它影响的其它特性, 看看是否应该补充一些集成测试.

Q: 有时候开发人员完成一个特性时已接近迭代结束, 测试人员没有时间进行充分测试, 怎么办?

A: 下个迭代测呗, 并且在计算开发速度时, 只应该计算本迭代通过测试人员验收的特性, 那些仅仅是开发人员完成, 没有经过测试人员充分测试的特性不计在内. 这种情况是不可避免的. 但我们能通过一些手段让测试与开发更加同步, 尽量缩短滞后性, 包括让测试人员与开发人员更紧密合作, 尽量让测试用例自动化等.

Q: 我还是觉得在开发迭代过程中, 测试人员的工作量不饱满.

A: 如果这不是您的感觉, 而是事实, 并且前面测试人员必须要做的工作也都做了, 还是不饱满, 那么恭喜你, 可以省下一些测试人员, 去做别的事了. 但不推荐的是, 不要让测试人员同时为两个团队工作. 这会大大增加沟通的成本. 你会经常发现, 当你的开发者想找测试人员协助时, 却找不到人了, 于是你的团队便被堵塞在那里. 而测试人员本身的Context切换也是痛苦的.

Q: 你们说验收测试应该由客户来编写, 可在我们这里根本不可能.

A: 验收, 当然是由客户来验收, 这在理论上是毫无疑问的, 而且肯定在各行各业发生着. 只是具体到测试用例的编写和执行, 无论是自动化的还是非自动化的, 都需要掌握一定的技术, 需要周密的思考, 需要专门的时间, 客户可能无法同时满足这几个条件, 我们要尽力争取, 争取不到, 便只好通过更充分的交流来弥补越俎代庖的失真. 这时业务分析人员和测试人员要通力合作, 完成验收测试的编写.

Q: 你们说你们之前的项目产品代码和测试代码的比例大约 1:3, 这不是平白增加了 3 倍的工作量吗?

A: 是增加了 3 倍的代码量而不是工作量. 它节省了你几十人做几个月庞大的预先设计的工作量, 节省了你详细设计每个模块并为之编写几百页详设文档的时间, 节省了无数不眠之夜通宵Debug的时间, 它节省了集成阶段修复难以计数的Bug的工作量, 甚至它缩减了你产品代码的数量, 大量的重复代码被消除了, 大量过度设计的复杂代码被废除了, 你的代码更易理解了, 添加新特性更容易了, 发现的Bug更易定位了, 以致于大大减少了长达数年的生命周期内维护的工作量. 有点夸张了? 可这就是 TDD 和敏捷开发带给我们的好处(如果你已经实践了)和vision(如果你还在观望)

Q: 我们也做单元测试, 但是是先写产品代码后写测试的. 难道非得 TDD, 非得测试先行吗?

A: 没什么事是非做不可的. 取决于你要什么. TDD 只是以可验证的方式迫使你将质量内建在思维中, 长期的测试先行将历练你思维的质量. 而事后的单元测试只是惶恐的跟随者.

22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

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

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

沪ICP备05003035号

沪公网安备 31010102002173号