0%

这才是你要的分布式测试

说明

  • 前面文章介绍了如何利用docker搭建selenium grid

  • selenium grid由一个中心Hub节点和大于等于1个的Node节点组成,所有的Node节点都要注册到Hub节点。测试执行时所有的请求发送到Hub节点,Hub节点再将执行指令分发到多个注册的Node节点,Node节点是真正执行Web测试的节点,就相当于selenium本机执行一样。

  • 网上很多教程都是使用多进程/多线程启动多个node去执行用例,这样的意义并不大,如果一个Node中的用例太多,并不会节约多少时间,如果开启太多的进程用node去跑用例,无论是管理用例的复杂性和损耗资源都不是成正比

  • 正确的使用场景是一个node里面再去分布式执行用例,其实java中的testng提供这样的功能,而此次我介绍的是用python,因此需要集结合pytest

环境搭建

  • 本机window10安装好python3
  • pytest
  • pytest-html 生成测试报告插件
  • pytest-xdist 分布式用例
1
2
3
pip install pytest
pip install pytest-xdist
pip install pytest-html
  • 修改pytest源代码文件,解决报告乱码问题
1
2
3
4
5
6
D:\app\Python37\Lib\site-packages\pytest_html\plugin.py

class TestResult:
def __init__(self, outcome, report, logfile, config):
#self.test_id = report.nodeid.encode("utf-8").decode("unicode_escape")
self.test_id = re.sub(r'(\\u[a-zA-Z0-9]{4})',lambda x:x.group(1).encode("utf-8").decode("unicode-escape"),report.nodeid)
  • 看下我的代码结构

image-20220325182337774

  • 核心目录是testcase是用例目录,里面分为了大回归、小回归、冒烟文件夹,用例放不同的用例,这样的放的好处非常明显了,大回归包含小回归和冒烟,小回归包含冒烟
  • testcase目录下由conftest.py 这里面对pytestpytest-html可以进行有些设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# conftest.py
import pytest
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from py._xmlgen import html

_driver = None


# @pytest.fixture()
@pytest.fixture(scope='session', autouse=True)
def driver():
global _driver
print(11111)
ip = "远程ip"
server = "http://%s:7777/wd/hub" % ip
# ip = "localhost"
_driver = webdriver.Remote(
command_executor="http://%s:7777/wd/hub" % ip,
desired_capabilities=DesiredCapabilities.CHROME
)
# 返回数据
yield _driver
# 实现用例后置
_driver.close()
_driver.quit()


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
"""
当测试失败的时候,自动截图,展示到html报告中
:param item:
"""
if not _driver:
return
pytest_html = item.config.pluginmanager.getplugin('html')
outcome = yield
report = outcome.get_result()
report.description = str(item.function.__doc__)
extra = getattr(report, 'extra', [])

if report.when == 'call' or report.when == "setup":
xfail = hasattr(report, 'wasxfail')
if (report.skipped and xfail) or (report.failed and not xfail):
screen_img = _capture_screenshot()
if screen_img:
html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:1024px;height:768px;" ' \
'onclick="window.open(this.src)" align="right"/></div>' % screen_img
extra.append(pytest_html.extras.html(html))
report.extra = extra


def pytest_html_results_table_header(cells):
cells.insert(1, html.th('用例名称'))
cells.insert(2, html.th('Test_nodeid'))
cells.pop(2)


def pytest_html_results_table_row(report, cells):
cells.insert(1, html.td(report.description))
cells.insert(2, html.td(report.nodeid))
cells.pop(2)


def pytest_html_results_table_html(report, data):
if report.passed:
del data[:]
data.append(html.div('通过的用例未捕获日志输出.', class_='empty log'))


def pytest_html_report_title(report):
report.title = "pytest示例项目测试报告"

def _capture_screenshot():
"""
截图保存为base64
:return:
"""
return _driver.get_screenshot_as_base64()
  • 用例编写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# test_selenium.py

mport os
import time
import pytest

class TestCase(object):
@pytest.mark.finished
def test_001(self, driver):
time.sleep(3)
driver.get("https://www.baidu.com")
print(driver.title)
driver.find_element_by_id("kw").click()
driver.find_element_by_id("kw").send_keys("你好")
def test1_001(self, driver):
time.sleep(3)
driver.get("https://www.baidu.com")
print(driver.title)
driver.find_element_by_id("kw").click()
driver.find_element_by_id("kw").send_keys("你好")
  • 代码运行入口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# runner.py

import os
from multiprocessing import Process

import pytest


def main(path):
# 这里的-n 3 意思就是同时并发3个用例执行
pytest.main(['%s' %path,'-n 3', '--html=report.html','--self-contained-html', '--capture=sys'])


if __name__ == '__main__':
# 大回归
test_case = Process(target=main, args=("d:\\project\\py_selenium_grid\\testcase\\大回归\\",))
test_case.start()
test_case.join()
# 小回归
...
# 冒烟
...
  • 看下代码运行结果

image-20220325182511617

  • 看下本地的测试报告,错误的自动截图

image-20220325182640854

image-20220325182619284

其他

  • pytest-xdist 经过测试,在云服务器(双核)上跑,可以正常跑,如果指定进程太大,会造成容器内存泄漏,服务器出现长期卡死,所以建议:每次执行任务时,都把容器删了重建,同时进程不要指定太大
    • 可以进入到docker 容器中排除内存情况:docker exec -it ec3d30bff042 top,其中ec3d30bff042 selenium/node-chrome 的镜像
  • 测试了pytest-parallel 这个无论是在服务器还是本地win上跑,都报错
  • 使用了pytest-multithreading 发现个问题
    • pytest-html上的记录日志,会被打乱,因此如果要使用的化,建议在conftest.py中,记录日志的代码去掉
    • 多线程访问百度网站,会被限制输入验证信息
    • 安装pip install pytest-multithreading -i https://pypi.douban.com/simple
    • 调用 pytest -s testcase/大回归/小回归/冒烟 --th 10 --html=report.html --self-contained-html --capture=sys

总结

  • 当然如果没有条件,你对本地搭建selenium grid有兴趣的化,可以参考我之前的这篇文章,源代码都提供好了
  • 后续工作就更加简单,比如jenkins启动,我会对接一个可视化平台实现:任务管理,报告分析等