unittest
unittest 的底层代码是基于 C 语言实现的,源码中看不到详细的代码,但是我们可以发现 TestCase、TestSuite、TestRnner 三个类中都有 run 方法,而且 run 方法的作用的都是执行测试用例。
TestRunner 可以执行单条测试用例以及测试套件中所有的用例
TestSuite 是直接执行套件中所有的用例
Testcase 中的 run 方法是执行当前这条测试用例
不管是 TestRunner 还是 TestSuite 的 run 方法执行用例,最终还是调用了用例本身的 run 方法去执行的
通过TestLoader
实现用例发现,TestSuite
组织执行顺序,TextTestRunner
驱动测试流程,最终由TextTestResult
收集结果。理解其类继承关系和执行链是进行高级定制(如并行测试、自定义报告)的关键。建议结合inspect
模块分析测试类的元数据,或通过继承TestCase
重写核心方法实现深度定制
多线程执行用例的实现思路
思路一:创建多个测试套件,每个套件使用一个线程去执行报告
优缺点:
优点:同一个测试用例类中用例执行的先后顺序能够得到保障
缺点:用例需要自己手动添加到套件,然后分配给各个线程,会出现用例数量分配不均,线程资源浪费,
思路二:所有的用例收集到测试套件,使用多线程去执行套件的测试用例,
优缺点:
优点:多线程共享用例资源,能够充分利用多线程的资源
缺点:用例执行的先后顺序不好控制
测试结果整合
前面我们在执行测试套件的时候,是通过 TestRunner 去执行的,TestRunner 的 run 方法的参数只能传入测试用例或者测试套件,而 unittest 中 TestSuite,和 TestCase 的 run 方法在调用的时候,可以接收一个叫做 TestResult 的对象。而用例执行的结果就是保存在这个 TestResult 对象中。我们如果要整个测试结果可以自己先创建一个 TestResult 对象,然后执行套件的时候传进入,最后两个套件执行得到的测试结果都会保存在这一个 TestResult 对象中。
# 创建一个结果保存对象
res = unittest.TestResult()
# 运行测试套件返回测试结果
t1 = threading.Thread(target=suite1.run,kwargs={"result":res})
t2 = threading.Thread(target=suite2.run, kwargs={"result": res})
t1.start()
t2.start()
t1.join()
t2.join()
print(res)
代码封装
import unittest
from concurrent.futures.thread import ThreadPoolExecutor
from case_test.test_case import TestLogin, TestRegister
def run_test(suite, thread_count=1):
res = unittest.TestResult()
# 创建一个线程池,执行测试用例
with ThreadPoolExecutor(max_workers=thread_count) as ts:
for case in suite:
# 将用例的执行任务提交到线程池中
ts.submit(case.run, result=res)
return res
if __name__ == '__main__':
# 创建两个套件
suite1 = unittest.defaultTestLoader.loadTestsFromTestCase(TestRegister)
# 给根据套件的数量,每个套件创建一个线程去执行
res = run_test(suite=suite1, thread_count=3)
# 打印测试结果
print(res)
多线程执行如何生成测试报告呢?目前 unittest 生成测试报告使用的几个开源的库,比如 BeautifulReport,HTMLTestRunner 都不支持多线程,但是可以自己定制化改造呢,实现多线程
unittestreport 多线程执行用例
使用 unittestreport 来多线程执行用例非常简单,只需要在执行用例时加一个参数 thread_count,指定执行的线程即可
但是但是,unittestreport 开多线程数执行,实际进程并行数还是1,达不到我们想要的效果,上面提到定制化HTMLTestRunner ,就是为了让一个测试用例函数,在参数驱动下,可以开启多线程进程并行,比如,设置线程数为2,会同时启动2个不同的参数用例,打开两个模拟器窗口,并行执行测试步骤。从而真正实现,多任务并行的功能,并且保证生成的测试报告能正确记录测试的顺序,不会出现不同参数测试的步骤的截图顺序交错的问题等等
pytest
平常我们功能测试用例非常多时,比如有1千条用例,假设每个用例执行需要1分钟,如果单个测试人员执行需要1000分钟才能跑完
当项目非常紧急时,会需要协调多个测试资源来把任务分成两部分,于是执行时间缩短一半,如果有10个小伙伴,那么执行时间就会变成十分之一,大大节省了测试时间
为了节省项目测试时间,10个测试同时并行测试,这就是一种分布式场景
分布式执行用例的原则:
用例之间是独立的,没有依赖关系,完全可以独立运行
用例执行没有顺序要求,随机顺序都能正常执行
每个用例都能重复运行,运行结果不会影响其他用例
采用pytest的插件pytest-xdist来进行多进程的并发执行测试用例
pytest-xdist分布式测试的原理
前言
1、xdist的分布式类似于一主多从的结构,master机负责下发命令,控制slave机;slave机根据master机的命令执行特定测试任务。
2、在xdist中,主是master,从是workers。
大致原理
1、xdist会产生一个或多个workers,workers都通过master来控制。
2、每个worker负责执行完整的测试用例集,然后按照master的要求运行测试,而master机不执行测试任务。
pytest-xdist分布式测试的流程
第一步:创建worker
1、master会在总测试会话(test session)开始前产生一个或多个worker。
2、master和worker之间是通过execnet和网关来通信的。
3、实际编译执行测试代码的worker可能是本地机器也可能是远程机器。
第二步:收集测试项用例
1、每个worker类似一个迷你型的pytest执行器。
2、worker会执行一个完整的test collection过程。【收集所有测试用例的过程】
3、然后把测试用例的ids返回给master。【ids表示收集到的测试用例路径】
4、master是不会执行任何测试用例集的。
注意:分布式测试(pytest-xdist)方式执行测试时不会输出测试用例中的print内容,因为主机并不执行测试用例,pycharm相当于一个master。
第三步:master检测workers收集到的测试用例集
1、master接收到所有worker收集的测试用例集之后,master会进行一些完整性检查,以确保所有worker都收集到一样的测试用例集(包括顺序)。
2、如果检查通过,会将测试用例的ids列表转换成简单的索引列表,每个索引对应一个测试用例的在原来测试集中的位置。
3、这个方案可行的原因是:所有的节点都保存着相同的测试用例集。
4、并且使用这种方式可以节省带宽,因为master只需要告知workers需要执行的测试用例对应的索引,而不用告知完整的测试用例信息。
第四步:测试用例分发
–dist-mode选项
each:master将完整的测试索引列表分发到每个worker。
load:master将大约25%的测试用例以轮询的方式分发到各个worker,剩余的测试用例则会等待workers执行完测试用例以后再分发
注意:可以使用pytest_xdist_make_scheduler 这个hook来实现自定义测试分发逻辑。
第五步:测试用例的执行
1、workers 重写了 pytest_runtestloop :pytest的默认实现是循环执行所有在test session这个对象里面收集到的测试用例。
2、但是在xdist里, workers实际上是等待master为其发送需要执行的测试用例。
3、当worker收到测试任务, 就顺序执行 pytest_runtest_protocol 。
4、值得注意的一个细节是:workers 必须始终保持至少一个测试用例在的任务队列里, 以兼容 pytest_runtest_protocol(item, nextitem) hook的参数要求,为了将 nextitem传给hook。
5、worker会在执行最后一个测试项前等待master的更多指令。
6、如果它收到了更多测试项, 那么就可以安全的执行 pytest_runtest_protocol ,因为这时nextitem参数已经可以确定。
7、如果它收到一个 “shutdown”信号, 那么就将 nextitem 参数设为 None, 然后执行 pytest_runtest_protocol
第六步:测试用例再分发(–dist-mode=load)
1、当workers开始/结束执行时,会把测试结果返回给master,这样其他pytest hook比如: pytest_runtest_protocol就可以正常执行。
2、master在worker执行完一个测试后,基于测试执行时长以及每个work剩余测试用例综合决定是否向这个worker发送更多的测试用例
第七步:测试结束
1、当master没有更多执行测试任务时,它会发送一个“shutdown”信号给所有worker。
2、当worker将剩余测试用例执行完后退出进程。
3、master等待所有worker全部退出。
4、然而此时仍需要处理诸如 pytest_runtest_logreport 等事件。
pytest实现多线程运行测试用例(pytest-parallel)
常用参数配置
① –workers=n :多进程运行需要加此参数, n是进程数。默认为1
② –tests-per-worker=n :多线程需要添加此参数,n是线程数
如果两个参数都配置了,就是进程并行;每个进程最多n个线程,总线程数:进程数*线程数
【注意】
①在windows上进程数永远为1。
②需要使用 if name == “main” :,在dos中运行会报错(即在命令行窗口运行测试用例会报错)
示例:
pytest test.py –workers 3 :3个进程运行
pytest test.py –tests-per-worker 4 :4个线程运行
pytest test.py –workers 2 –tests-per-worker 4 :2个进程并行,且每个进程最多4个线程运行,即总共最多8个线程运行
pytest-parallel与pytest-xdist对比说明
① pytest-parallel 比 pytst-xdist 相对好用,功能支持多。
② pytst-xdist 不支持多线程;
③pytest-parallel 支持python3.6及以上版本,所以如果想做多进程并发在linux或者mac上做,在Windows上不起作用(Workers=1),如果做多线程linux/mac/windows平台都支持,进程数为workers的值
TestNG
一个是 testng 另一个是 junit5,个人喜欢比较喜欢 testng,对于组织测试用例有很高的灵活性
invocationCount 参数
testng 中@Test注解标明的为测试用例,Test 后可以跟上各种参数,比如 invocationCount 可以控制多线程,invocation 表示调用,即调用次数
threadPoolSize 要配合 invocationCount 使用,表示线程池大小,即是几线程
@Test(invocationCount=1, threadPoolSize=2)
使用两线程去运行一次
parallel 设置线程级别
首先我们要在 mvn 的 pom.xml 中配置 surefire 插件,具体 surefire 插件的如何结合 testng 做测试可以参考 surefire 官网,或者结合我之前写的一篇博文
https://blog.csdn.net/abcnull/article/details/106715004
由于 surefire 中可以指定运行哪个 testng.xml 测试套件(不指定默认是 testng.xml 的名字),在 testng.xml 中我们可以指定测试套件的多线程运行的运行级别
下面我指定线程并行的隔离级别是 tests,然后使用 3 线程
<suite name="WebUI Test Suite" parallel="tests" thread-count="3">
parallel 还有其他哪些并行级别呢?
methods
所有@Test测试用例都会施行多线程,对应 xml 中的 method 中 include 标签指定的测试方法,即测试用例
tests
所有 test 标签的用例会被指定运行在一个线程里,不同 test 标签即为不同的线程
classes
一个 class 标签即为开启一个新的线程,对应一个测试类(测试类中会有多个测试用例方法)
instances
每一个实例即为一个线程
最常用的还是 tests 级别并行,实际中,我会把配置文件读取操作放在 BeforeSuite 中,然后在 BeforeTest 中做驱动初始化,因为 tests 级别并行的时候,BeforeTest 在不同线程都会执行一次,而 BeforeSuite 只会在第一个线程执行一次
其他
断点调试
if name == ‘main‘:
import pdb; pdb.set_trace()
unittest.main()