《Effective Python》笔记-第9章 测试与调试
第七十五条:通过repr字符串输出调试信息
如果要用print调试程序,那么类型的区别就很重要,应该打印的是对象的repr版本。该函数会返回对象的可打印表示形式,把repr返回的值传给内置的eval函数,会得到一个跟原来相同的Python对象(实际中eval()函数的使用必须相当谨慎)。
在格式化字符串里用%s处理相关的值,就跟把这个值传给str函数一样,都能得到一个便于认读的字符串。如果用%r处理,那么得到的就是repr字符串。在f-string中,也可以用值来取代其中有待替换的那一部分,并产生便于认读的那种字符串,但如果待替换的部分加了!r后缀,那么替换出来的就是repr字符串。
给类定义__repr__特殊方法,可以让print函数把该类实例的可打印表现形式展现出来,在实现这个方法的时候,还可以提供更为详尽的调试信息。
第七十六条:在TestCase子类里验证相关的行为
在Python中编写测试的最经典办法是使用内置的unittest模块。unittest模块里有个TestCase类,我们可以定义它的子类,并在其中编写多个test方法,以便分别验证想要测试的每一种行为。TestCase子类的这些test方法名称都必须以test这个词开头。
TestCase类还提供了许多辅助方法,例如,可以在test方法中通过assertEqual辅助方法来确认两个值相等,而不采用内置的assert语句。
可以用subTest辅助方法做数据驱动测试,这样就不用针对每项子测试重复编写相关的代码与验证逻辑了。
第七十七条:把测试前、后的准备与清理逻辑写在setUp、tearDown、setUp-Module与teartDownModule中,以防用例之间互相干扰
单元测试验证的是每项功能是否正常,集成测试验证的是模块之间是否能正确交互,这两种测试都很重要。
把测试用例准备与清理工作放在setUp与tearDown方法中,可以避免用例之间相互干扰,使它们都能从一套干净的环境开始执行。
集成测试的准备与清理工作可以放在模块级别的setUpModule与tearDownModule函数里,系统在测试该模块与其中所有TestCase子类的过程中,只会把这两个函数各自运行一遍。
第七十八条:用Mock来模拟受测代码所依赖的复杂函数
unittest.mock模块中的Mock类能够模拟某个接口的行为,我们可以用它替换受测函数所要调用的接口,因为那些接口可能不太容易在测试的过程中配置。
如果mock把受测代码所依赖的函数替换掉了,那么在测试的时候,不仅要验证受测代码的行为,而且还要验证它有没有正确地调用这些mock,这可以通过Mock.assert_called_once_with等一系列方法实现。
要想把受测函数所调用的其他函数用mock逻辑替换掉,一种办法是给受测函数设计只能以关键字来制定的参数;另一种办法是通过unittest.mock.patch系列的方法暂时隐藏那些函数。
第七十九条:把受测代码所依赖的系统封装起来,以便于模拟和测试
在写单元测试的时候,如果总要反复使用许多代码来注入模拟的逻辑,那么可以考虑把受测函数所要用到的逻辑封装到类中,因为封装之后更容易注入。
Python内置的unittest.mock模块里有个Mock类,他能模拟类的实例,这种Mock对象具备与元类中的方法相对应的属性。如果在它上面弄调用某个方法,就会触发相应的属性。
如果想把程序完整测一遍,那么可以重构代码,在原直接使用复杂系统的地方引入辅助函数,让程序通过这些函数来获取它要用的系统,这样我们就可以通过辅助函数注入模拟逻辑。
第八十条:考虑用pdb做交互调试
在程序里某个兴趣点直接调用Python内折叠breakpoint函数就可以触发交互调试器。
Python的交互调试界面(即pdb界面)也是一套完整的Python执行环境,在它里面我们可以检查正在运行的程序处于什么状态,并予以修改。
我们可以在pdb界面里面用相关的命令精准控制程序的执行方式,这样就能做到一边检查一边推进程序了。
pdb模块还能够在程序出现错误的时候检查该程序的状态,这可以通过python -m pdb -c continue “
第八十一条:用tracemalloc来掌握内存的使用与泄露情况
不借助相关的工具,我们可能很难了解Python程序是怎么使用内存的,以及其中有些内存是如何泄露的。
gc模块可以帮助我们了解垃圾回收器追踪到了哪些对象,但他不能告诉我们那些对象是如何分配的。
Python内置的tracemalloc模块提供了一套强大的工具,可以帮助我们更好的了解内存的使用情况,并找到这些内存分别由哪一行代码所分配。