测试框架的设计有两种思路,一种是自底向上,从脚本逐步演变完善成框架,这种适合新手了解框架的演变过程。另一种则是自顶向下,直接设计框架结构和选取各种问题的解决方案,这种适合有较多框架事件经验的人。本章和下一张分别从两种设计思路来介绍框架的搭建过程。
从脚本到用例
相比于一堆测试脚本,使用规范化的测试用例格式会方便我们灵活的执行和管理用例。一个完整的自动化测试用例应包含:
- 测试准备(setup):测试准备步骤、用例辅助方法或工具,可以共用;
- 测试步骤(test steps):核心测试步骤;
- 断言(assertions):期望结果于实际结果的比对,用例可以报告不止一个断言;
- 测试清理(teardown):对执行测试造成的影响进行清理和还原,以免影响后续执行,可以共用。
编写测试函数
将测试脚本转化为Pytest测试用例的方法非常简单,只要将测试过程编写为test开头的测试函数即可。
有时候我们为了快速实现一个功能,会直接把代码按步骤写到模块里,如下例:
代码test_baidu_search_v0.9.py内容
from selenium import webdriver
from time import sleep
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
driver.find_element_by_id('kw').send_keys('博客园 韩志超')
driver.find_element_by_id('su').click()
sleep(1)
if '韩志超' in driver.title:
print('通过')
else:
print('失败')
driver.quit()
然后我们开启第一步优化,首先,可以把步骤写到一个函数里,这样方便在脚步中写多个用例,另外,我们可以按照Pytest测试框架用例到写法,写成标准的用例。期望结果的判断我们使用标准的assert断言语句,修改后如下:
代码test_baidu_search_v1.0.py内容
from selenium import webdriver
from time import sleep
def test_baidu_search_01():
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
driver.find_element_by_id('kw').send_keys('博客园 韩志超')
driver.find_element_by_id('su').click()
sleep(1)
assert '韩志超' in driver.title, '标题不包含韩志超' # 自定义失败消息
driver.quit()
不同于v0.9版Python脚本的运行方法(命令行使用python <脚步路径>),Pytest用例脚本使用pytest <脚本路径>或python -m pytest <脚本路径>来执行。
我们也可以在Pytest用例脚本下面加上以下语句,
if __name__ == '__main__':
pytest.main([__file__])
这样便可以像Python脚本一样直接运行。其中__file__指当前脚本,也可以添加其他运行参数,如-qs等。
使用断言
测试用例中必须包含期望结果来验证执行的通过与否。不同于“调试”,需要有人值守来人工判断没个执行过程是否通过,自动化“测试”往往需要批量运行,并自动判断用例是否通过。断言即是执行过程中的实际结果与期望结果的自动对比。
Pytest中使用标准的assert语句来进行断言。assert断言语句在用例执行失败时(和期望结果不一致)会抛出AssertionError异常,测试框架会自动捕获该异常,并将用例标记为执行失败状态,并且不会因为异常导致执行中断而影响其他用例的执行。
注:在用例中也可以使用if判断配合pytest.fail()或者手动抛出AsserionError异常来将用例设置为失败状态,示例如下:
if '韩志超' not in driver.title:
# rasie AssersionError('标题不包含韩志超')
pytest.fail('标题不包含韩志超')
Web UI自动化测试过程中常用的断言策略有以下几种:
- 流程成功执行视为通过:按确定的元素操作步骤,可以正常完成整个流程视为通过;
- 通过标题断言:通过当前网页标题driver.title来判断处于某一页面上;
- 通过URL断言:通过当前URL,driver.current_url来判断处于某一页面上;
- 通过页面源码断言:通过网页源代码driver.page_source中包含特定信息来判断处于某一页面上;
- 通过存在特定元素断言:通过存在某个特定元素来判断处于某一页面上。
通过元素判断是否在某一页面上的示例如下:
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
def test_open_baidu():
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
try:
driver.find_element_by_id('kw') # 尝试定位搜索框
except NoSuchElementException:
pytest.fail('不存在搜索框')
在框架中,可以将常用的断言方法进行封装以方便使用。
分离测试准备及清理方法
在测试用例中,我们要尽可能的分离核心测试步骤,将可以共用的测试准备及测试清理步骤单独提取出来,以方便复用。
在上例中,我们核心的测试步骤是从打开百度网站到断言网页标题,而启动浏览器和关闭浏览器可以视为测试准备和测试清理方法。
测试准备和测试清理方法我们可以使用Pytest中的setup_function()及teardown_function()方法,也可以使用自定义的Fixture方法来吧两个方法集中的一个函数中,如下例:
代码test_baidu_search_v3.py内容
from time import sleep
from selenium import webdriver
import pytest
def setup_function():
global driver
driver = webdriver.Chrome()
def teardown_function():
driver.quit()
def test_baidu_search_01(driver):
driver.get("https://www.baidu.com")
driver.find_element_by_id('kw').send_keys('博客园 韩志超')
driver.find_element_by_id('su').click()
sleep(1)
assert '韩志超' in driver.title
if __name__ == '__main__':
pytest.main([__file__])
使用自定义Fixture方法
代码test_baidu_search_v4.py内容
from time import sleep
from selenium import webdriver
import pytest
@pytest.fixture
def driver():
dr = webdriver.Chrome()
yield dr
dr.quit()
def test_baidu_search_01(driver):
driver.get("https://www.baidu.com")
driver.find_element_by_id('kw').send_keys('博客园 韩志超')
driver.find_element_by_id('su').click()
sleep(1)
assert '韩志超' in driver.title
if __name__ == '__main__':
# --html需要pip install pytest-html
pytest.main([__file__, '--html=report.html','--self-contained-html'])
上例中我们自定义了一个名为driver的Fixture方法。yield上面对的所有语句属于测试准,这里创建了一个浏览器驱动对象dr。yield语句将dr对象交给用例执行,并等待用例执行完毕,再执行下面的测试清理语句,退出浏览器。
用例中使用Fixture函数名driver作为参数来注入测试准备和测试清理方法,用例中使用的driver即Fixture函数yield返回的dr,浏览器驱动对象。
使用Pytest-selenium插件
Pytest框架的优点之一是,拥有很多功能丰富的插件。使用这些插件可以省略我们自己编写Fixture方法的过程,直接安装使用。
上例中我们自己编写了一个名为driver的fixture方法,我们也可以直接使用Pytest-Selenium插件,该插件提供了一个全局的driver(或selenium)Fixture方法,可以直接使用,并且支持切换使用的浏览器。安装Pytest-Selenium插件,并修改代码如下:
代码test_baidu_search_v5.py内容
from time import sleep
from selenium import webdriver
import pytest
def test_baidu_search_01(driver):
driver.get("https://www.baidu.com")
driver.find_element_by_id('kw').send_keys('博客园 韩志超')
driver.find_element_by_id('su').click()
sleep(1)
assert '韩志超' in driver.title
if __name__ == '__main__':
# --html需要pip install pytest-html
# --driver 需要pip install pytest-selenium
pytest.main([__file__, '--driver=chrome', '--html=report.html','--self-contained-html'])
pytest-selenium还支持配置浏览器选项及配合pytest-html失败自动截图等功能,详细可以参考其官方使用文档https://pytest-selenium.readthedocs.io/en/latest/。
注:pytest-selenium默认会拦截所有接口请求,可以在pytest.ini中配置sensitive_url = ''来设置无敏感url。
生成测试报告
使用Pytest框架生成测试报告最常用的插件有pytest-html和allure-pytest两种,前者简单,可以生成单文件测试报告。后者华丽,功能强大,使用较为复杂。本章我们使用pytest-html来生成报告,allure-pytest的具体使用下章讲解。
pytest-html的使用方式非常简单,安装pytest-html并使用--html来生成报告即可:
if name == 'main':
# --html需要pip install pytest-html
pytest.main([file, '--html=report.html','--self-contained-html'])
注:如果想自己生成HTML测试报告,可以在conftest.py文件中通过pytest_terminal_summary钩子方法terminalreporter参数对象的stats属性结合三方库Jinjia2来自定义生成报告。