pytest--xdist
分布式执行用例的设计原则
1.用例之间是独立的,用例之间没有依赖关系,用例可以完全独立运行【独立运行】
2.用例执行没有顺序,随机顺序都能正常执行【随机执行】
一、基本使用
1.安装pytest-xdist:
pip install -U pytest-xdist
2.运行:
pytest test_se.py -n NUM
其中NUM填写并发的进程数
二.参数
1. -n auto:可以自动检测到系统的CPU核数(逻辑处理器的数量)
说明:使用auto等于利用了所有CPU来跑用例,此时CPU占用率会特别高
2. --dist=loadscope: 默认是无序执行的,可以通过该参数来控制顺序
说明:将按照同一个模块module下的函数和同一个测试类class下的方法来分组,然后将每个测试组发给可以执行的worker,确保同一个组的测试用例在同一个进程中执行
目前无法自定义分组,按类class分组优先于按模块module分组
3.--dist=loadfile
说明:按照同一个文件名来分组,然后将每个测试组发给可以执行的worker,确保同一个组的测试用例在同一个进程中执行
三.流程原理
和大多数的分布式系统相似, xdist里有master和worker的概念.
master负责整个测试任务的调度, 测试报告等工作;
worker则是实际执行测试的宿主进程.
具体的测试执行的流程如下:
1.起始阶段:
2.收集测试项:
每个worker是个迷你的pytest runner对象. workers这时会执行一个完整test collection过程, 然后将结果发回到master(master本身不做测试收集工作).
3.测试收集检查:
master收到这些节点发回的结果后, 执行一些sanity检查以确保所有worker节点都收集到相同的测试项(包括顺序). 当所有的检查都通过后, 再将这些测试项转换为一个简单的索引列表, 每个索引对应一个测试项的在原来测试集中的位置. 这个方案可行的原因是所有的节点都保存着相同的测试集, 并且使用这种方式可以节省带宽, 因为master只需要告知节点需要执行的测试项对应的索引, 而不用告知完整的测试项信息.
FAQ环节其实提到, 在各个node上单独执行测试收集工作是因为如果在master上执行测试收集,那么就需要作很多序列化处理, 因为worker是进程级的. 这会使问题复杂化, 并且使pytest变得不易于维护.
4.测试分发:
如果dist-mode是each, 那么这时master只需将完整的列表发送给每个节点.
如果dist-mode是load, 那么这时master会将大约25%的测试项以轮询的方式发往各个worker. 剩余的测试项则会等待workers执行完测试以后分发, 见下文.
注意: pytest_xdist_make_scheduler 这个hook可以用于实现自定义的分发逻辑.
5.测试执行:
workers 重写了 pytest_runtestloop: pytest的默认实现基本上是循环执行所有在session这个对象里面收集到的测试项, 但是在xdist里, workers实际上是等待master为其发送需要执行的测试项的. 当worker收到测试任务, 就顺序执行 pytest_runtest_protocol. 值得注意的一个细节是:workers 必须始终保持至少一个测试项在的任务队列里, 以兼容pytest_runtest_protocol(item, nextitem) hook的参数要求.为了将 nextitem传给hook, worker会在执行最后一个测试项前等待master的更多指令.如果它收到了更多测试项, 那么久可以安全的执行 pytest_runtest_protocol , 因为这时nextitem参数已经可以确定. 如果它收到一个 "shutdown"信号, 那么就将 nextitem 参数设为 None, 然后执行pytest_runtest_protocol .
6.测试分发(Load模式):
当测试项在 workers里的开始/结束执行时, 测试结果会发回到master, 这样其他pytest hooks比如pytest_runtest_logstart和 pytest_runtest_logreport就可以正常执行.master (处于load的dist-mode时)在节点执行完一个测试后, 基于测试执行时长以及每个节点剩余测试项综合决定是否向这个节点发送更多的测试项.
7.测试结束:
当master没有更多待执行测试项时, 它会发送一个"shutdown"信号给所有workers, worker将剩余的测试项执行完毕并退出进程. master则一直等待workers全部退出, 当然此时任然需要处理诸如pytest_runtest_logreport等事件.
总之, 牢记config对象是进程间独立的, 但是report对象之间的值可以互相同步的, 但是要避免同步对象;