- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议75:利用测试驱动开发提高代码的可测性
测试驱动开发(Test Driven Development,TDD)是敏捷开发中一个非常重要的理念,它提倡在真正开始编码之前测试先行,先编写测试代码,再在其基础上通过基本迭代完成编码,并不断完善。其目的是编写可用的干净的代码。所谓可用就是能够通过测试满足基本功能需求,而干净则要求代码设计良好、可读性强、没有冗余。在软件开发的过程中引入TDD能带来一系列好处,如改进的设计、可测性的增强、更松的耦合度、更强的质量信心、更低的缺陷修复代价等。那么,如何在编程过程中实施测试驱动开发呢?一般来说,遵循如图7-2所示的过程。
图7-2 测试驱动开发流程
1)编写部分测试用例,并运行测试。
2)如果测试通过,则回到测试用例编写的步骤,继续添加新的测试用例。
3)如果测试失败,则修改代码直到通过测试。
4)当所有测试用例编写完成并通过测试之后,再来考虑对代码进行重构。
假设开发人员需要编写一个求输入数列的平均数的例子。在开始编写测试用例之前我们先编写简单的编码以保证测试能真正开始执行。
假设源文件avg.py内容如下:
def avg(x): pass
当完成基本的代码框架之后,便可以开始实施TDD的具体过程了。
步骤1 编写测试用例。基本的测试用例应该包括对整数、浮点数、混合输入情况下基本功能的验证,以及对空输入、无效输入的处理。测试用例代码如下:
import unittest from avg import avg class TestAvg(unittest.TestCase): def test_int(self): print "test average of integers:" self.assertEqual(avg([0,1,2]),1) def test_float(self): print "test average of float:" self.assertEqual(avg([1.2,2.5,0.8]),1.5) def test_empty(self): print "test empty input:" self.assertFalse(avg([]),False) def test_mix(self): print "test with mix input:" self.assertEqual(avg([-1,3,7]),3) def test_invalid(self): print "test with invalid input:" self.assertRaises(TypeError,avg,[-1,3,[1,2,3]]) if __name__ == '__main__': unittest.main()
步骤2 运行测试用例(部分输出),测试结果显示失败,如图7-3所示。
图7-3 测试结果
步骤3 开始编码直到所有的测试用例都通过。这是一个不停地重复迭代的过程,需要多次重复编码测试,直到上面的测试用例全部执行成功。
def avg(x): if len(x)<=0: print "you need input at least one number" return False sum = 0 try: for i in x: sum += i except TypeError: raise TypeError("your input is not value with unsupported type") return sum/len(x)
步骤4 重构。在测试用例通过测试之后,现在可以考虑一下重构的问题了。代码有没有更优化的实现方式呢?显然直接利用内建函数sum更高效直接。因此对代码进行重构并重新测试生成最终产品代码。
def avg(*x): if len(*x)<=0: print "you need input at least one number" return False try: return sum(*x)/len(*x) except TypeError: raise TypeError("your input is not value with unsupported type")
关于测试驱动开发和提高代码可测性方面有以下几点需要说明:
TDD只是手段而不是目的,因此在实践中尽量只验证正确的事情,并且每次仅仅验证一件事。当遇到问题时不要局限于TDD本身所涉及的一些概念,而应该回头想想采用TDD原本的出发点和目的是什么。
测试驱动开发本身就是一门学问,不要指望通过一个简单的例子就掌握其精髓。如果需要更深入的了解,推荐在阅读相关书籍的同时在实践中不断提高对其的领悟。
代码的不可测性可以从以下几个方面考量:实践TDD困难;外部依赖太多;需要写很多模拟代码才能完成测试;职责太多导致功能模糊;内部状态过多且没有办法去操作和维护这些状态;函数没有明显返回或者参数过多;低内聚高耦合;等等。如果在测试过程中遇到这些问题,应该停下来想想有没有更好的设计。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论