75. 通过repr字符串输出调试信息
通常print()打印的时候,默认是调用类中的repr方法的,如果想要观察具体信息,可以自己修改类中的该方法。如果这个类不受控制,可以打印类.dict查看属性和值,dir(类)看方法,这两个里面的“类”应该也可以换成具体对象
class A:
y = 2
def __init__(self):
self.x = 1
a = A()
print(dir(a))
print(dir(A))
print(a.__dict__)
print(A.__dict__)
76. 在TestCase子类里验证相关的行为
在Python中编写测试的最经典办法是使用内置的unittest模块。以下为测试utils模块中to_str方法,其中一项测试用例失败,并不影响系统继续执行TestCase子类里的其他test方法,所以我们最后能够看到总的结果,知道其中有多少项测试用例成功,多少项失败,而不是只要遇到测试用例失败,就立刻停止整套测试。
定义的测试方法需要以test开头,不用于测试的辅助方法不要以test开头,assertEqual测试结果是否相等,assertRaise测试抛出的异常是否一致,
TestCase类还提供了subTest辅助方法,可以让我们把相似的用例全都写在同一个test方法中,让它们成为这个用例中的子用例,这样的话,每个子用例所共用的那部分代码与逻辑只需要写一次就行。
77. 把测试前、后的准备与清理逻辑写在setUp、tearDown、setUp-Module与tearDownModule中,以防用例之间互相干扰
TestCase子类在执行其中的每个test方法之前,经常需要先把测试环境准备好,即测试这些方法的前置条件或环境,可以在TestCase子类中覆写setUp与tearDown方法,并把相应的准备逻辑与清理逻辑写在里面。系统在执行每个test方法之前都会先调用一遍setUp方法,并在执行完test方法之后调用一遍tearDown方法。
比如可以把创建临时目录放在setUp中,测试完后,用tearDown删除临时目录
当程序变复杂后,单纯的单元测试可能就不够了,需要集成测试,可以使用mock等工具,对于集成测试来说,测试环境的准备与清理工作可能要占用大量计算资源,并持续比较长的时间,这时采用setUp和tearDown可能就不够了。可以使用setUpModule和tearDownModule,高成本资源只需要在这些模块中初始化一次就好,下面的代码可以看出setUpModule和tearDownModule只执行一次就行。
78. 用Mock来模拟受测代码所依赖的复杂函数
测试的时候,一些复杂逻辑很难从开发环境真实获取或者使用起来很慢,这样的逻辑可以用mock函数和mock类来模拟。mock可以模拟数据库行为,这样测试的时候就不需要实实在在的连接数据库了,当然mock还能模拟抛出异常等其他行为
79. 把受测代码所依赖的系统封装起来,以便于模拟和测试
之前用mock来模拟某个函数或方法,因此mock中的spec属性设置为方法名,调用时就和调用方法一样,而遇到这种复杂测试的事后,代码往往相对复杂,且需要编写很多实例,导致初次阅读代码的人会感到难以理解。可以将这些测试函数封装到类里面,然后令spec=classname,这样在调用里面的方法就和对象调用的方式一样,用点方法名调用。
80. 考虑用pdb做交互调试
通过print或单元测试来测试所编写的代码无法发现所有错误,Python内置的交互调试器(interactive debugger)就是一种强大的工具,它可以检查程序状态,打印局部变量的值,还可以每次只执行一条Python语句(也就是单步执行)。
在其他大部分编程语言中,如果要使用调试器,那么必须先在源文件中指定断点,令程序在执行到这一行时停下来。然而Python不用这样,你可以直接在认为有问题的那行代码前加入一条指令,让程序暂停,并启动调试器,这是最简单的办法。采用这种办法来调试程序,与正常启动程序并没有什么区别。
where:打印出当前的执行调用栈(execution call stack),可以据此判断程序当前执行到了哪个位置,以及程序是在调用了哪些函数后才触发breakpoint断点的。
up:把观察点沿着执行调用栈上移一层,回到当前函数调用者处,以观察位于当前断点之上的那些层面分别有什么样的局部变量。
down:把观察点沿着执行调用栈下移一层。检查完程序的运行状态后,可以通过下面这五条命令决定程序接下来应该如何执行:
step:执行程序里的下一行代码,并在执行完毕后把控制权交还给调试器。如果下一行代码带有函数调用操作,那么调试器就会停在受调用的那个函数开头。
next:执行当前函数的下一行代码,并在执行完毕后,返回交互调试界面。如果下一行代码带有函数调用操作,系统不会令调试器停在受调用的函数开头。
return:让程序一直运行到当前函数返回为止,然后把控制权交还给调试器。
continue:让程序运行到下一个断点处(那个断点可以是通过breakpoint触发的,也可以是在调试界面里设置的)。
quit:退出调试界面,并且让接受调试的程序也随之终止。如果已经找到了问题,那么就可以用这个命令结束调试。如果发现寻找的方向不对,或者需要先去修改程序的代码,那么也应该运行这个命令以便重新调试。
python3 -m pdb -c continue
81. 用tracemalloc来掌握内存的使用与泄漏情况
可能会有程序没有及时释放不再引用的数据而导致内存耗尽。
可以采用gc模块把垃圾回收器目前知道的对象都列出来,gc.get_objects函数的缺点在于,它并没有指出这些对象究竟要如何分配。在比较复杂的程序中,同一个类的对象可能是因为好几种不同的原因而为系统所分配的。知道对象的总数固然有意义,但更为重要的是找到分配这些对象的具体代码,这样才能查清内存泄漏的原因。
import gc
print(len(gc.get_objects()))
a = list(range(100))
print(len(gc.get_objects()))
for i in gc.get_objects()[:3]:
print(i)
tracemalloc可以解决gc的缺点,他能追溯对象到分配他的位置,下面的代码,可以查看到当前文件下第8行,即变量a所占用的内存size和变量数count,可以利用traceback打印栈追踪信息。
import gc
import tracemalloc
tracemalloc.start(10)
snap1 = tracemalloc.take_snapshot()
a = [1] * 100
snap2 = tracemalloc.take_snapshot()
diff = snap2.compare_to(snap1, 'lineno')
print(diff[0])
print('\n'.join(diff[0].traceback.format()))