- 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章 性能剖析与优化
建议73:理解单元测试概念
单元测试用来验证程序单元的正确性,一般由开发人员完成,是测试过程的第一个环节,以确保所写的代码符合软件需求和遵循开发目标。好的单元测试可以带来以下好处:
减少了潜在bug,提高了代码的质量。经过了严格的单元测试,意味着代码中潜在的bug数量大大减少,同时单元测试能够使得你的思考方式不同于编码,因此能够很快发现不合理的设计或者逻辑,以及算法方面的漏洞或者故障,从而为编写高质量代码提供基本保障。
大大缩减软件修复的成本。我们知道在软件开发生命周期越早阶段发现问题或缺陷,其修复的代价越小,因此在单元测试阶段发现问题后修复代价要远远小于在集成测试或者系统测试阶段的代价。
为集成测试提供基本保障。单元测试可以大大减少程序中各个部件的不可靠性,通过先测试程序部件再测试部件组装,使集成测试变得更加简单,测试人员因此可以将更多的精力放在用户场景上。
纵然单元测试有各种好处,事实却往往是“理想很丰满,现实很骨感”。实际应用中,单元测试的实践并不理想,原因是多方面的:一则管理层重视不够,根本没有把单元测试提升到和系统集成测试同样的高度;二则是迫于项目期限的压力,开发人员往往没有更多的时间来写单元测试的用例和代码;三则开发人员本身存有趋利避害的侥幸心理,他们更关注于可以工作的代码,一旦编码完成,便迫切地希望能够进行集成工作,因为这样进度看起来更快,同时寄希望于集成测试去发现程序中潜在的问题。那么,到底应该怎么样进行有效的单元测试呢?有效的单元测试应该从以下几个方面考虑:
1)测试先行,遵循单元测试步骤。测试不应该是编码结束后再来考虑的事情,实际上从项目需求阶段就应该开始考虑。编写单元测试应该尽量安排在项目的早期,并且测试代码应该先于被测试的代码,这样更有利于明确需求。典型的单元测试的步骤如下:
创建测试计划(Test Plan)。
编写测试用例,准备测试数据。
编写测试脚本。
编写被测代码,在代码完成之后执行测试脚本。
修正代码缺陷,重新测试直到代码可接受为止。
2)遵循单元测试基本原则。常见的原则如下:
一致性。意味着1000次执行和一次执行的结果应该是一样的,因此,类似于currenttime=time.localtime(),产生这种不确定执行结果的语句应该尽量避免。
原子性。意味着单元测试的执行结果返回只有两种,True或者False,不存在部分成功、部分失败的例子。如果发生这样的情况,往往是测试设计得不够合理。
单一职责。测试应该基于情景(scenario)和行为,而不是方法。如果一个方法对应着多种行为,应该有多个测试用例;而一个行为即使对应多个方法也只能有一个测试用例。例如下边的代码。
testMethod(): assertTrue(behaviour1) assertTrue(behaviour2)
应该改为:
testMethodCheckBehaviour1 (): assertTrue(behaviour1) testMethodCheckBehaviour2 (): assertTrue(behaviour2)
隔离性。不能依赖于具体的环境设置,如数据库的访问、环境变量的设置、系统的时间等;也不能依赖于其他的测试用例以及测试执行的顺序,并且无条件逻辑依赖。单元测试所有的输入应该是确定的,方法的行为和结果应是可以预测的。因此要避免以下的测试例子:
testMehodBeforeOrAfter(): if before: assertTrue(behaviour1) elif after: assertTrue(behaviour2) else: assertTrue(behaviour3)
修改为:
testMethodBefore(): before = True assertTrue(behaviour1) testMethodAfter(): after= True assertTrue(behaviour2) testMethodNow(): after= False before = False assertTrue(behaviour3)
3)使用单元测试框架。Python测试也曾经历过“蛮荒时代”,那个时候测试完全是个人化的行为,没有统一的框架标准,每个用Python构建的项目在编写和运行测试方面都采用自己的习惯做法。这种做法不仅效率低下,而且不利于项目管理。幸好后来Python社区出现了一些测试套件,提供约定和通用标准,后面逐渐演变为流行的测试框架。在单元测试方面常见的测试框架有PyUnit等,它是Kent Beck和Erich Gamma所设计的JUnit的Python版本,在Python 2.1之前需要单独安装,Python 2.1之后它成为一个标准库,名为unittest。它支持单元测试自动化,可以共享地进行测试环境的设置和清理,支持测试用例的聚集以及独立的测试报告框架。我们以unittest来看看如何借助单元测试框架更好地进行单元测试。unittest相关的概念主要有以下4个:
测试固件(test fixtures)。测试相关的准备工作和清理工作,基于类TestCase创建测试固件的时候通常需要重新实现setUp()和tearDown()方法。当定义了这些方法的时候,测试运行器会在运行测试之前和之后分别调用这两个方法。
测试用例(test case)。最小的测试单元,通常基于TestCase构建。
测试用例集(test suite)。测试用例的集合,使用TestSuite类来实现,除了可以包含TestCase外,也可以包含其他TestSuite。
测试运行器(test runner)。控制和驱动整个单元测试过程,一般使用TestRunner类作为测试用例的基本执行环境,常用的运行器为TextTestRunner,它是TestRunner的子类,以文字方式运行测试并报告结果。
来看一个简单实例。假设要测试下述类:
class MyCal(object): def add(self,a,b): return a+b def sub(self,a,b): return a-b
首先编写测试用例,并在setUp()方法中完成初始化工作,在tearDown()方法中完成资源释放相关的工作。我们采用动态方法编写测试类,多个测试方法可以集成在一个类中,这些方法按习惯通常以test开头。具体如下:
class MyCalTest(unittest.TestCase): def setUp(self): print "running set up" self.mycal = mycal.MyCal() def tearDown(self): print "running teardown" self.mycal = None def testAdd(self): self.assertEqual(self.mycal.add(-1,7), 6) def testSub(self): self.assertEqual(self.mycal.sub(10,2), 8)
在创建了一些TestCase子类的实例作为测试用例之后,下一步要做的工作就是用TestSuit类来组织它们。TestSuite类可以看成是TestCase类的一个容器,用来对多个测试用例进行组织,这样多个测试用例可以自动在一次测试中全部完成。
suite = unittest.TestSuite() suite.addTest(MyCalTest("testAdd")) suite.addTest(MyCalTest("testSub"))
在编写完测试用例及组织好测试用例之后,现在可以执行测试了。
runner = unittest.TextTestRunner() runner.run(suite)
运行命令python-m unittest-v MyCalTest,得到测试结果的输出如下:
testAdd (MyCalTest.MyCalTest) ... running set up running teardown ok testSub (MyCalTest.MyCalTest) ... running set up running teardown ok ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK
当然实际执行测试过程和应用测试框架比上面的例子要复杂得多。读者可以在Python文档中查看更多关于unittest使用的详细信息,并在实际工作中实践。
最后需要强调的是,单元测试绝不是浪费时间的无用功,它是高质量代码的保障之一,在软件开发的环节中值得投入精力和时间把好这一关。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论