有些读者可能会问为什么会在这儿突然加入python多线程/多进程的介绍呢?是为了效率,减少整个测试用例的执行时间。
在web测试中,不可避免的一个测试就是浏览器兼容性测试,在没有自动化测试前,我们总是在一台或多台机器上安装N种浏览器,然后手工在不同的浏览器上验证主业务流程和关键功能模块功能,以检测在不同浏览器或不同版本浏览器上,我们的web应用是否可以正常工作。
另外,当我们的项目测试用例比较多时,假设有500个用例,每个用例执行完需要30秒,那么整个项目的用例跑完就需要4个多小时,想想还是很慢的。
这时如果我们引进多线程技术,那么理论上,每多开一个线程,测试时间就能节约一倍。本节就将具体介绍一下,多线程在我们自动化测试中的使用。
但这块内容是真正的高级应用,理解起来会有点困难,包括笔者都理解的不是很透彻。所以,思来想去,决定只介绍一个容易使用,且易于理解的多线程解决方案
在使用多线程技术之前,我们首先要理解什么是线程?这里笔者给出两个学习链接,就不在这儿去一一介绍了。
菜鸟教程关于多线程的介绍:Python3 多线程 | 菜鸟教程
一位博友讲解的多进程多线程:搞定python多线程和多进程 - morra - 博客园
对多线程不了解的读者朋友,还请先打开链接学习一下,然后继续后文的学习。
1、多线程在浏览器兼容性测试中的应用
跨浏览器测试是功能测试的一个分支,用以验证web应用在不同浏览器上的正常工作,通常情况下,我们都期望web应用能够被我们的用户在任何浏览器上使用,例如有的人喜欢IE浏览器上使用,有的人喜欢Firefox或者有的人喜欢Chrome。但事实是,因为如下的部分原因,使得我们的web应用在这些浏览器上很多地方并不相互兼容。
以上几点轻微的是页面样式显示不一样,严重的会导致某些功能不可用。那么如何解决跨浏览器兼容性测试?下面我们基于Python的多线程技术来尝试启动多个浏览器进行自动化测试。我们以百度搜索为实例来介绍,代码如下:
from selenium import webdriver
import threading
import time
# 测试用例
def test_baidu(browser):
driver = None
if browser == 'ie':
driver = webdriver.Ie()
elif browser == 'firefox':
driver = webdriver.Firefox()
elif browser == 'chrome':
driver = webdriver.Chrome()
elif browser == 'edge':
driver = webdriver.Edge()
if driver is None:
exit()
driver.implicitly_wait(5)
driver.get("https://www.baidu.com")
driver.maximize_window()
time.sleep(2)
driver.find_element_by_id('kw').send_keys('selenium')
driver.find_element_by_id('su').click()
time.sleep(3)
print("已在 {} 浏览器中执行完毕!".format(browser))
driver.quit()
if __name__ == "__main__":
browsers = ['ie', 'firefox', 'chrome', 'edge', 'opera']
# 构建线程并启动执行
for browser in browsers:
th = threading.Thread(target=test_baidu, args=(browser,))
threads.append(th)
th.start()
执行上面的脚本,我们会发现用例会在不同的浏览器中并行执行,相较手工测试,甚至比单线程测试要快很多,这就是多线程技术的魅力所在。
2、多线程在批量执行用例中的应用
我们前面讲过,当需要执行大量的测试用例时,单线程的去跑用例,速度依然很慢。为此,我们引进多线程技术。笔者参考了很多关于多线程和多进程在自动化测试中应用的资料,综合比较下来,只发现一种方案是比较简单易用的,也是能很方便整合到我们原有已搭建好的自动化测试框架的。即引入新的可完美结合多线程技术的测试报告生成模块——BeautifulReport模块。
BeautifulReport模块下载地址:GitHub - TesterlifeRaymond/BeautifulReport: 适用于unittest自动化测试的可视化报告。下载好之后放到D:\Python37\Lib\site-packages目录下。
BeautifulReport模块是GitHub上的一位大神和其好友共同开发的,可完美解决使用多线程技术测试时测试报告的生成问题。如果使用多线程技术进行自动化测试,我们前面介绍的生成测试报告的HTMLTestRunner模块将在此结束它的历史使命了,因为它不支持多线程技术在自动化测试中的使用(有兴趣的可以去尝试解决一下)。
嗯~,笔者根据自己的喜好和对我们现有框架的适配,对BeautifulReport模块做了稍许优化,感兴趣的可以到我的资源下载优化过的BeautifulReport模块:BeautifulReport-Web服务器文档类资源-CSDN下载很方便整合到selenium自动化测试框架的,完美结合多线程技术的测试报告生成模块——Beautif更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/SCF_1104/74482493
优化如下:
那么怎么使用这个模块呢?我们只需要重构一下run_allcase.py文件就好了,这就是框架中各个部分都模块化的好处,当代码需要改动时,只需改动相对应的模块就好了。
具体的修改后的run_allcase.py代码如下:
from public.common import send_mail # 引入public.common中邮件发送功能
from BeautifulReport import BeautifulReport # 引入BeautifulReport模块
import unittest, time, os, threading
# 获取当前时间,并转化成我们想要的格式
now_time = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime(time.time()))
# 拼接测试报告名,固定名TestReport + 当前时间
report_name = 'TestReport_' + now_time + '.html'
# 拼接测试报告存放路径
report_path = os.path.join(os.getcwd(), 'result\\testreport')
# 使用discover方法加载所有test_开头的测试用例
def add_suite():
case_file = os.path.join(os.getcwd(), 'testcase')
discover = unittest.defaultTestLoader.discover(
case_file,
pattern='test_*.py',
top_level_dir=None
)
return discover
# 调用BeautifulReport模块执行测试用例,并生成测试报告
def run_case(suites):
result = BeautifulReport(suites)
result.report(filename=report_name,
tester='非攻',
title='自动化测试报告',
description='百度、CSDN、慕课用例批量执行测试',
log_path=report_path
)
# 使用多线程去执行测试用例
def start_thread(suites):
threads = []
# 创建线程,并启动
for test_case in suites:
t = threading.Thread(target=run_case, args=(test_case,))
t.start()
threads.append(t)
# 等所有线程完成用例执行
for t in threads:
t.join()
# 封装一个主函数
def main():
test_suites = add_suite()
start_thread(test_suites)
# 所有用例执行完成后,将生成的测试报告通过邮件发送
filename = '{}\\{}'.format(report_path, report_name)
text = "最新Web自动化测试报告请接收,见附件。"
send_mail(text, filename)
if __name__ == "__main__":
main()
从上面代码我们可以看到,我们对run_allcase.py进行比较大的重构,重构的主要思路是对用例的读取加载、用例的执行、多线程的使用等分别进行封装,使代码逻辑更清晰、更有条理。
执行一下我们新改造的run_allcase.py脚本,我们会看到三个用例文件test_baidu.py、test_imooc.py、test_csdn.py中的用例会同时执行,执行完毕会在testreport目录下生一个新的测试报告,如下图。同时我们的自动发送邮件功能,会把测试报告以附件的方式发送给你设定的接收者。
有没有发现新的测试报告要比原来的更好看呢?关于多线程技术的使用就介绍到这儿了。不熟悉多线程的读者,还请先去学习了解一下多线程部分的知识,懂个基础就行了。
这部分学会实例中的用法就好,可以不去深究。
3、补充:BeautifulReport模块提供的截图功能
此部分介绍的非多线程内容,前面有提到,BeautifulReport模块有一个截图功能,可以将截图放在测试报告对应的用例中,这个功能是极好的,所以在这里补充介绍一下。
有时我们在测试中,当用例执行失败时,我们总希望在回溯失败用例的时候有依有据,这时候除了测试执行的日志,还需要有一个更直观的屏幕截图。在第五章第四节我们有介绍过截图的API,这里我们想要的是把截图整合到我们的测试报告中,该怎么实现?
BeautifulReport模块提供了一个API——add_test_img(),在对应的测试用例外挂一个装饰器就好了。对于截图的存放,我们在result目录下新建一个img目录用于存放截图。那么如何使用这个截图功能呢?具体使用如下,我们以test_baidu.py中的用例改造示例。
from BeautifulReport import BeautifulReport # 引入BeautifulReport模块
from selenium import webdriver
import unittest
import time, os
class TestBaidu(unittest.TestCase):
img_path = 'result\\img'
# 传入一个img_name(图片名), 并存储到默认的文件路径下(.\web_auto\result\img)
def save_img(self, img_name):
self.driver.get_screenshot_as_file(
'{}\\{}.png'.format(os.path.abspath(self.img_path), img_name)
)
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(10)
self.driver.get("https://www.baidu.com")
# 打开百度, 点击新闻按钮前截一次图,在点击新闻按钮后再截一次图
# 这里add_test_img参数即相应的截图名称,必须要与save_img参数一致
@BeautifulReport.add_test_img('点击新闻按钮前', '点击新闻按钮后')
def test_news(self):
"""新闻按钮跳转"""
self.save_img('点击新闻按钮前')
self.driver.find_element_by_link_text('新闻').click()
self.save_img('点击新闻按钮后')
self.assertIn('百度新闻', self.driver.title)
# 如果在测试过程中, 出现不确定的错误, 程序会自动截图, 并返回失败。
# 此时add_test_img参数必须是用例方法名,这个名字也将是截图名
@BeautifulReport.add_test_img('test_map')
def test_map(self):
"""地图按钮跳转"""
# 此处故意设置使定位失败
self.driver.find_element_by_link_text('地图的').click()
# 此处故意使校验失败
self.assertIn('百度地图', self.driver.title)
# 如果用例没有出现错误, 即使用了错误截图装饰器, 也不会影响用例的使用,也不会截图
@BeautifulReport.add_test_img('test_academic')
def test_academic(self):
"""学术按钮跳转"""
self.driver.find_element_by_link_text('学术').click()
self.assertIn('百度学术', self.driver.title)
def tearDown(self):
time.sleep(3)
self.driver.quit()
仔细阅读上面的改造过的test_baidu.py脚本代码,这里截图装饰器的使用,分了两种情况:
一是已预知用例中某个地方可能出现错误,指定在预知出错地方前后调用截图save_img(),如脚本中的test_news()用例。这种情况装饰器的参数值要与save_img()中参数值一致,这个是作为截图名称的,一定要一致;
二是不确定用例中哪儿会出现错误,只外挂一个装饰器,如脚本中test_map()和test_academic()两个用例。这种情况装饰器中的参数值要与测试用例方法名一致,同样的,这也是作为截图名称的。另外,若所挂装饰器的测试用例没有出错,则不会截图,对测试用例也不会有影响。
改造完test_baidu.py脚本代码,我们执行run_allcase.py,用例跑完之后,我们会发现在\web_auto\result\img目录下多了三个截图,如下图。
我们再打开新生成的测试报告,分别展开test_map 和 test_news用例,会发现下面也出现了对应的截图,如下两张图,这就是我们要的效果。
但个人觉得,这个功能尽量少用,当用例多起来时,这个功能会出现一些不稳定的错误,另外加载的截图过多,也会导致测试报告页面加载卡顿。