pytest-xdist进程级并发过程及参数化说明--成都-阿木木

关于分布式插件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(运行的进程数量)

  • -n auto:可以自动获取系统的CPU核数,启用该参数CPU占用率会非常高,每个进程执行速度会非常慢,所以,worker越多,并不会按照理论时间来进行测试脚本运行效率的提升

  • -n x:手动指定CPU数量


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~gw1gw0~gw2gw0~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

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值