使用pytest-xdist实现分布式WEB自动化测试

前言

pytest-xdist是一款优秀的分布式测试插件,它可以实现进程级别的并发,也可以实现类似于master-worker主从分布式测试。目前中文网站对于进程级别的并发介绍的比较多,对于主从分布式测试的资料少之又少。经过反复的实践,对于主从分布式环境的部署和运行有了一定的认知,因此,在本文中将着重介绍主从分布式测试,对于进程并发只做简单的介绍

进程并发

使用pytest命令

pytest -s -n 2   #-n 后面跟核数,如果你的CPU是4核的,那么2表示使用2个核来并发2个进程执行测试
pytest -s -n auto  #auto会自动检测你的CPU核数,如果是4核,将并发4个进程

获取安装包

这里面有一些Linux下的安装包,还有我自己用来练手的demo项目WEB_AutoTest
链接:https://pan.baidu.com/s/14vnqtQufXr2Jj1HuACJa9A
提取码:bgdp
其他资料没有准备,因此在开始试验前需要自行安装 pytest,pytest-html,pytest-xdist,selenium

项目介绍

项目结构

WEB_AutoTest
  |--test_cases
     |--__init__.py
     |--test_caculate.py
     |--search.py
  |--pytest.ini

项目介绍

test_caculate.py是让python自己不停的做次方运算,这是试水项目,不建议一上来就执行web自动化,先搞个demo试试,能运行起来再搭建web自动化环境

# test_caculate.py
import pytest

@pytest.mark.parametrize("n", list(range(50000)))
def test_baidu(n):
    a = 2 ** 32
    print(f"the baidui-{n}.")


@pytest.mark.parametrize("n", list(range(50000)))
def test_sina(n):
    b = 4 ** 32
    print(f"the sina-{n}")

search.py的作用是使用无头浏览器不停的打开百度,然后输入关键字,查找网页响应的元素,最后做断言。当test_caculate.py通过之后,可以将其名字更改为test_search.py,将前者改成caculate.py,这样就只会运行web自动化测试

# search.py
from selenium import webdriver
import pytest

@pytest.mark.parametrize("n", list(range(100)))
def test_baidu(n):
    # 创建chrome参数对象
    options = webdriver.ChromeOptions()
    options.add_argument('--no-sandbox')  # 解决DevToolsActivePort文件不存在的报错
    # options.add_argument('window-size=1600x900') # 指定浏览器分辨率
    options.add_argument('--disable-gpu')  # 谷歌文档提到需要加上这个属性来规避bug
    options.add_argument('--hide-scrollbars')  # 隐藏滚动条, 应对一些特殊页面
    options.add_argument('blink-settings=imagesEnabled=false')  # 不加载图片, 提升速度
    options.add_argument('--headless')  # 浏览器不提供可视化页面. linux下如果系统不支持可视化不加这条会启动失败

    driver = webdriver.Chrome(options=options)

    driver.get('http://www.baidu.com')
    title = driver.title
    url = driver.current_url
    #输入百度
    driver.find_element_by_id("kw").send_keys("百度")
    elements = driver.find_elements_by_xpath("//h3//em")
    for element in elements:
        assert element.text == "百度"
    #点击百度一下
    driver.find_element_by_id("su").click()

    assert title == "百度一下,你就知道"
    assert url == "https://www.baidu.com/"
    assert type(n) == int
    driver.quit()

pytest.ini是一个配置文件,稍后会说明其作用

# pytest.ini
[pytest]
#addopts = --tx socket=192.168.0.109:8888

Windows + Linux主从分布式

系统环境

一开始,我选择了本机Windows做master,虚拟机Centos7做worker1,同时还克隆了一台Centos7作为worker2。有关的环境版本如下:

角色 系统 Python版本 ip
master Windows7 v3.7.3 192.168.0.101
worker1 Centos7.6 v3.8.0 192.168.0.109
worker2 Centos7.6 v3.8.0 192.168.0.126

同步测试用例并运行

既然要做主从分布式,那么就需要master将测试用例同步给worker。在官网上有两种同步方式:通过远程的SSH账号和通过远程的socket服务。前者我琢磨了比较久,发现怎么试都不成功,于是就直接试后者,后者在官网上的介绍基本能让人看懂:

基本上分为两步:
1.将socketserver.py文件上传到你的服务器,然后这样运行:

python socketserver.py

socketserver.py的文件在我分享的安装包里有,只需下载下来,通过rz命令上传到你的服务器上。我放在了/opt目录下,随便放在哪个目录都行,只要你记得路径就行。接着运行socketserver.py,socket服务就启动了,开始监听8888端口

2.然后在本机(Windows)上运行同步命令

pytest -d --tx socket=192.168.0.109:8888 --tx socket=192.168.0.126:8888 --rsyncdir test_cases test_cases

在工程目录下打开命令行运行。一开始还是好的,没多久Centos7上的socket服务就挂了


截两张socket服务挂了的图
worker1

worker2

有个细节值得注意pytest -d --tx socket=xxxx --rsyncdir xxxx xxxx,这个命令在同步完之后,会自动收集用例并执行。虽然执行的过程挂了,但用例确实同步成功了。就在下图的pyexecnetcache目录下

关于这个服务挂了的问题,github上已经提了一些issue:
With different python versions rsync can hangs on
pytest hangs indefinitely after completing tests in parallel
通过对第一个问题的查看,发现是由于Python版本不一致导致的。接着我更新了两台Centos7上的版本,将它们都改为Python v3.7.7,发现还是会报同样的错误。然后我设想,可以再克隆一台虚拟机,三台Centos7,一个master,两个slave,这样行不行呢?

三台Linux主从分布式

系统环境

这里的Python版本无论是v3.7.7还是v3.8.0都行,只要一致就可以

角色 系统 Python版本 ip
master Centos7.6 v3.8.0 192.168.0.109
worker1 Centos7.6 v3.8.0 192.168.0.126
worker2 Centos7.6 v3.8.0 192.168.0.136

分布式运行

重新运行之前,注意将项目上传到master上

在worker1和worker2分别运行socket.server
接着进入项目目录WEB_AutoTest,开始执行同步命令

pytest -d --tx socket=192.168.0.126:8888 --tx socket=192.168.0.136:8888 --rsyncdir test_cases test_cases

这次正常了,我们可以看到这些运行截图
master

worker1

worker2

运行结束之后,发现总共运行用例100000条,耗时8min52s。其中,worker1运行了47540条,约占47%,耗时8min41s,worker2运行了52460条,约占52%,耗时8min40s
master

worker1

worker2

单进程运行

我现在使用master单独运行这100000条用例,看看效果

可以看到运行100000条用例,master单进程跑只花了4min24s

多进程运行

如果我给master分配4核,跑2个进程和跑4个进程呢?
2个进程

4个进程

计算次方耗时对比

可以看出,计算型的用例,好像是CPU核数也多,时间越慢。另外分布式的作用好像也不大

环境 核数 用例数量 耗时 备注
Centos7主从分布式 1 100000 8min52s 1 Centos7 master + 2 Centos7 worker
Centos7单进程 1 100000 4min22s
Centos7双进程并发 2 100000 8min38s
Centos7四进程并发 4 100000 11min09s

WEB自动化分布式

安装goole-chrome-stable

将安装包内的google-chrome-stable_current_x86_64.rpm上传到/opt目录下,使用yum安装

yum install ./google-chrome-stable_current_x86_64.rpm

安装成功后,可以使用下面的命令来查看chrome版本

[root@localhost opt]# google-chrome --version
Google Chrome 81.0.4044.122

安装chromedriver

将安装包内的chromedriver_linux64.zip上传到服务器,解压后将它放在/opt/Python-3.8.0/bin目录下,这个是python可执行文件所在的目录,已经配置环境变量了
同样可以使用命令查看chromedriver对应的版本,这个版本和chrome版本一定要对应好

[root@localhost opt]# chromedriver --version
ChromeDriver 81.0.4044.69 (6813546031a4bc83f717a2ef7cd4ac6ec1199132-refs/branch-heads/4044@{#776})

注意:以上说的chrome和chromedriver,三台机器都需要安装

修改模块名

要运行WEB自动化了,不希望执行次方运算,可以使用mv命令重命名下模块名

在一切就绪前,先在本地跑个简单的程序试试水,建议把test_search.py中的参数化改成1次,直接在master运行,看配置的环境有没有问题

正常情况下,运行完应该断言成功

分布式运行

在试验之前,建议把test_search.py的参数化运行次数改成原来的100

方法和前面一样,启动worker上的socket服务后,使用命令

pytest -d --tx socket=192.168.0.126:8888 --tx socket=192.168.0.136:8888 --rsyncdir test_cases test_cases

可以看到,总共运行100条用例,总耗时2min42s,其中worker1运行用例52条,耗时2min42s,worker2运行用例28条,耗时2min40s
master

worker1

worker2

单进程运行

运行用例100条,耗时4min57s

多进程运行

2个进程
100条用例耗时2min50s

4个进程
4个进程甚至更快,总耗时才1min40s

不过还要考虑一种情况,就是分布式有个同步用例的过程,现在我把同步用例的过程去掉,试试分布式的时间。看样子也要2min32s

WEB运行耗时对比

环境 核数 用例数量 耗时 备注
Centos7主从分布式 1 100 2min42s 1 Centos7 master + 2 Centos7 worker
Centos7单进程 1 100 4min57s
Centos7双进程并发 2 100 2min50s
Centos7四进程并发 4 100 1min40s
Centos7主从分布式不同步 1 100 2min32s

配置文件

和pytest的pytest.ini一样,你可以在pytest.ini中做一些配置,比如想要三个进程执行就可以使用配置

[pytest]
addopts = -n3

如果之前已经同步了一次测试用例,这次想要直接运行,但又不想跟特别长的--tx怎么办

[pytest]
addopts = --tx socket=192.168.0.126:8888 --tx=socket=192.168.0.136:8888

这样配置之后,你可以根据情况来选择运行

pytest --dist=each

或者

pytest --dist=load

each模式和load模式

这两个模式的解释最后才找到,官网藏得比较隐蔽。你可以点击这里查看-->dist的模式
--dist=each:master将所有的测试用例都分发到各个worker上,相当于worker1执行所有的100条用例,worker2执行所有的100条用例。运行完master上显示的总用例个数是200条
--dist=load:master先将25%的用例以循环的方式分发给每个worker,剩下的用例等worker执行完之后再分发。有点nginx负载均衡的感觉,worker负载过高,master会将其负载降低一些,让其他worker分摊一点。这种模式运行的总用例个数是100条,worker1和worker2运行的用例数不定,有可能各是50条,有可能一个是48条,一个是52条。就像我们之前看到的那样
使用pytest -d --tx socket=xxxx rsyncdir xxxx xxxx这种同步方式运行,默认的是load模式

APP自动化分布式设想

WEB自动化之所以简单,是因为只要chromedriver和chrome版本一致,问题应该不是很大。但APP就不同了,APP涉及到的东西较多,首先必须要有真机或者模拟器,然后还要启动appium服务端,在脚本中将desired_caps(比如platformName,packageName,activityName,systemVersion等)传递给appium服务端,再由服务端返回driver来操作客户端
因此APP自动化也要保证环境的一致性,即所装的APP版本,模拟器系统版本、appium的端口号都要一致,因为都是一套代码推送到不同的worker。可以像之前那样继续使用docker,注意的是两个worker的appium容器的docker_name必须一样,这样才可以在脚本里使用docker docker_name的方式启动appium服务
还有一个问题是,如果master和worker都是Centos7,就要考虑模拟器怎么和远程的worker连起来,之前尝试过通过tcpip来连,是可以成功的。这个tcpip端口号可以设置不同,因为desired_caps里对这个deviceName没要求。这么来看,APP自动化也是具有可行性的
事实上,分布式测试对用例的独立性要求很高,尽量避免不必要的依赖,这也是UI自动化设计的原则

参考文章

《pytest-xdist官网》
《Pytest系列(16)- 分布式测试插件之pytest-xdist的详细》
《Pytest系列(17)- pytest-xdist分布式测试的原理和流程》
《CentOS7下无界面使用Selenium+chromedriver进行自动化测试》
《execnet官网》

posted @ 2020-04-25 20:10  cnhkzyy  阅读(2061)  评论(2编辑  收藏  举报