使用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官网》