关于分布式插件pytest-xdist
进程级并发参数化说明
UI
自动化脚本耗费时间较长,效率低下,我们该如何处理这种情况,提升测试效率,下面我会就分布式插件pytest-xdist
的一个动态参数化问题,简单说明一下pytest-xdist
的工作流程,以及数据收集过程,希望对大家有所帮助。
–成都-阿木木
说明:
场景:我有100个UI
自动化用例,假设每个用例执行时间为一分钟,那么我顺序执行需要执行100分钟,在敏捷开发模式下,耗时比较长,如果赶上项目上线,需要快速得到项目上线后的反馈,那么效率是非常低下的。使用pytest-xdist
可以指定多个worker,进行执行用例,我指定10个worker,那么我整个测试脚本执行下来的理论时间为10分钟(PS:只是理论时间),大大的提升了效率。
分布式用例设计原则
- 用例独立,独立运行
- 用例没有顺序,随机运行
- 用例重复运行,运行结束即清理
- 用例的参数化使用非动态数据(重点:下面讲)
pytest-xdist
安装
pip install pytest-xdist
pytest-xdist
使用
在pytest
的命令行参数中指定-n x(运行的进程数量)
pytest-xdist
分布式执行用例参数化说明
这是一个测试路由器的case,使用了pytest.mark.parametrize
进行参数生成,它的作用和ddt
数据驱动框架一样,会执行两次测试用例,第一次传递参数“”,第二次传递一个随机10位的字符串参数
@allure.story("Service")
@allure.severity("normal")
@allure.description("服务模块-路由器列表-新建路由器-序列号")
@allure.testcase("XXX", name="测试用例位置")
@allure.title("服务模块-路由器列表-新建路由器-序列号")
@pytest.mark.parametrize("router_sn", ("", PubMethod.random_string("bnuiowehosdc235342", 10)))
def test_617(self, login_page_class, service_page_class, function_driver, router_sn):
logging.info("进入测试")
login_page_class.mode_login_into_url()
service_page_class.click_free_alert_close_icon()
service_page_class.click_create_router_btn()
service_page_class.send_keys_router_name(PubMethod.random_string("hfbudsio2353256", 10))
service_page_class.send_keys_router_sn(router_sn)
service_page_class.click_router_alert_determine_btn()
tips_by_router_sn = service_page_class.get_tips_by_router_sn()
if len(router_sn) == 0:
AssertMethod.assert_equal_screen_shot(function_driver, (tips_by_router_sn, "请输入路由器序列号!"))
else:
AssertMethod.assert_equal_screen_shot(function_driver, (tips_by_router_sn, "请输入正确的路由器序列号!"))
这是pytest
的命令行启动参数,我指定了-n参数为auto,运行run.py
,框架路径:https://github.com/chineseluo/ui_auto_frame_v2
,可以自己下载测试
test_args = ['-s', '-q', '-n=auto', '--browser={}'.format(browser), '--browser_opt={}'.format(browser_opt),
'--type_driver={}'.format(type_driver)]
bringing up nodes...
[gw0] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw1] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw2] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw3] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw4] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw5] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw6] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw7] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
gw0 [41] / gw1 [41] / gw2 [41] / gw3 [41] / gw4 [41] / gw5 [41] / gw6 [41] / gw7 [41]
scheduling tests via LoadScheduling
=================================== ERRORS ====================================
____________________________ ERROR collecting gw1 _____________________________
Different tests were collected between gw0 and gw1. The difference is:
--- gw0
+++ gw1
@@ -22,7 +22,7 @@
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_615
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_616
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_617[]
-TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_617[45u333isnn]
+TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_617[iw543ui332]
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_618_1
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_618_2
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_618_3
____________________________ ERROR collecting gw2 _____________________________
Different tests were collected between gw0 and gw2. The difference is:
--- gw0
+++ gw2
@@ -22,7 +22,7 @@
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_615
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_616
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_617[]
-TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_617[45u333isnn]
+TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_617[is4no2hw55]
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_618_1
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_618_2
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_618_3
____________________________ ERROR collecting gw3 _____________________________
Different tests were collected between gw0 and gw3. The difference is:
--- gw0
+++ gw3
可以看到我一用启用了8个worker,gw0~gw7
,读取的系统默认CPU核数为8,gw0~gw1
、gw0~gw2
、gw0~gw3
…以及其他几个worker报错:
Different tests were collected between gw0 and gw1. The difference is:--- gw0 +++ gw1
可以看到在test_617的两条case异常,有一个+号和减号,分析xdist
用例收集,进程调度原理:
收集caseID
及进程分发说明:根据简单的示意图可以看出,每一个worker(gwxxx)
都会去进行一次标准caseID
的收集,整个caseID
的收集过程和pytest
本身收集caseID
的过程是一致的,每一个worker
收集完caseID
后发送给主节点(管理节点),主节点进行caseID
的检查,包括了测试数据的检查,保证每个worker
获取的case的数据是一致的,"generating tests twice must produce identical tests"
。在所有worker
的测试用例集合相同的前提下,进行测试任务的分发,主节点下发的不再是caseID
,而是测试case的索引,以方便告诉每个worker
执行哪一个测试用例。
那么现在可以知道该异常出现的原因了,也就是说pytest-xdist
在进行caseID
收集的时候发现,参数化的case的入参不一致,我们是随机生成的,它只支持静态的有序的入参,下面我们修改一下脚本进行验证。
@allure.story("Service")
@allure.severity("normal")
@allure.description("服务模块-路由器列表-新建路由器-序列号")
@allure.testcase("XXX", name="测试用例位置")
@allure.title("服务模块-路由器列表-新建路由器-序列号")
def test_617_1(self, login_page_class, service_page_class, function_driver):
logging.info("进入测试")
login_page_class.mode_login_into_url()
service_page_class.click_free_alert_close_icon()
service_page_class.click_create_router_btn()
service_page_class.send_keys_router_name(PubMethod.random_string("hfbudsio2353256", 10))
service_page_class.send_keys_router_sn("")
service_page_class.click_router_alert_determine_btn()
tips_by_router_sn = service_page_class.get_tips_by_router_sn()
AssertMethod.assert_equal_screen_shot(function_driver, (tips_by_router_sn, "请输入路由器序列号!"))
@allure.story("Service")
@allure.severity("normal")
@allure.description("服务模块-路由器列表-新建路由器-序列号")
@allure.testcase("XXX", name="测试用例位置")
@allure.title("服务模块-路由器列表-新建路由器-序列号")
def test_617_2(self, login_page_class, service_page_class, function_driver):
logging.info("进入测试")
login_page_class.mode_login_into_url()
service_page_class.click_free_alert_close_icon()
service_page_class.click_create_router_btn()
service_page_class.send_keys_router_name(PubMethod.random_string("hfbudsio2353256", 10))
service_page_class.send_keys_router_sn(PubMethod.random_string("bnuiowehosdc235342", 10))
service_page_class.click_router_alert_determine_btn()
AssertMethod.assert_equal_screen_shot(function_driver, (tips_by_router_sn, "请输入正确的路由器序列号!"))
检查日志,查看caseID
的收集是否异常,可以看到下面的日志中,用例的ID收集无异常,验证通过。
bringing up nodes...
[gw0] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw1] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw2] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw3] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw4] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw5] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw6] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
[gw7] Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
gw0 [41] / gw1 [41] / gw2 [41] / gw3 [41] / gw4 [41] / gw5 [41] / gw6 [41] / gw7 [41]
scheduling tests via LoadScheduling
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_606_1
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_605_1_and_2[普通用户]
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_606_2
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_606_4
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_606_3
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_606_5
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_605_1_and_2[管理员]
TestCases/Service/test_servicePageCase.py::TestServicePageCase::test_606_6
总结:
通过分析pytest-xdist
的参数化异常原因,可以了解到pytest-xdist
的工作流程原理,针对性的处理异常,如果需要使用到pytest-xdist
插件的小伙伴,可以看一下,做一下测试,进行简单验证加深了解。
欢迎加入测试交流群:夜行者自动化测试(816489363)进行交流学习QAQ
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)