1. 简介:
pytest是python的一种单元测试框架,比Python自带的Unittest框架使用起来更简洁,效率更高。根据pytest的官方网站介绍(官方文档:https://docs.pytest.org/en/latest/contents.html),它具有如下特点:
- 非常容易上手,入门简单,文档丰富,文档中有很多实例可以参考
- 能够支持简单的单元测试和复杂的功能测试
- 支持参数化
- 执行测试过程中可以将某些测试跳过(skip),或者对某些预期失败的case标记成失败
- 支持重复执行(rerun)失败的case
- 支持运行由nose, unittest编写的测试case
- 可生成html报告
- 方便的和持续集成工具jenkins集成
- 可支持执行部分用例
- 具有很多第三方插件,并且可以自定义扩展
2. 安装:
# 一般情况都是模式安装最新版的,对于特殊有版本依赖的可以使用package==版本号来指定版本
pip install pytest -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
# 查看安装package的版本号的命令很多,如下任意一个都可以
# 版本号:6.1.2
# 在cmd 命令行
pip list
pip show pytest
pytest --version
# 在python交互命令
import pytest
print(pytest.__version__)
3. 使用示例:
a. 测试函数:每个函数是一个测试用例,简单方便
# 待测试函数
def add(x, y):
return x+y
# test01 function
def test_add_01():
assert add(2, 8) == 10
# test02 funciton
def test_add_02():
assert add(3, 7) == 9
# 运行方式
# 方式1: 在测试case所在的文件下打开terminal 输入:pytest或者py.test
# 方式2:在pycharm中的输入
if __name__ == '__main__':
pytest.main(['-q', 'module.py']) # 当前文件的名称
# 方式3:按节点执行,pytest test_mod.py::TestClass::test_method
b. 测试类:把多个测试用例,写到一个测试类里
# file: test_class.py
class TestClass():
def test_class_1(self):
x = 1
assert 1 == 1
def test_class_2(self):
x = 3
assert 3 == "3"
# 运行方式
# 1. 进入文件所在的终端terminal
# 2. 输入:py.test -q test_class.py
4.运行规则:
a) 用例查找规则
- 查找当前目录及其子目录下以test_.py或_test.py文件,
- 找到文件后,在文件中找到以test开头函数并执行
- 可以指定运行指定测试文件、测试方法及测试函数
- unittest实现该方式需要自己写方法来实现, os.walk()
b) 用例运行方式:
# 1.查看pytest命令行参数,可以用pytest -h 或pytest --help
# 2.cmd 运行
# 方式1
pytest
# 方式2
py.test
# 方式3
python -m test_file.py
# 运行.py模块里面的某个函数
pytest test_mod.py::test_func
# 运行.py模块里面,测试类里面的某个方法
pytest test_mod.py::TestClass::test_method
# 3. pycharm运行
# pycharm设置:以pytest方式运行,需要改该工程设置默认的运行器:file->Setting->Tools->Python Integrated Tools->项目名称->Default test runner->选择py.test
if __name__ == "__main__":
pytest.main(['-q', 'test_pytest.py::test_add_01'])
# 常用参数:
# -x 遇到错误终止运行
# --maxfail=num 遇到num个错误后终止运行
# -q --quiet decrease verbosity 参数只显示结果,不显示过程
# -s 参数是为了显示用例的打印信息
5.用例规则:
- 测试文件以test_开头(以_test结尾也可以)
- 测试类以Test开头,并且不能带有 __init__方法
- 测试函数以test_开头
- 断言使用assert
- 所有的包package必须要有 __init__文件
6.测试用例的预置条件
setup和teardown可以实现在测试用例之前或之后加入一些操作,这种设置是整个脚本全局生效的,用法类似unittest的,但是比uinttest强大。
- 模块级(setup_module/teardown_module)开始于模块始末,全局的
- 函数级(setup_function/teardown_function)只对函数用例生效 --不在类中
- 类级(setup_class/teardown_class)只在类中前后运行一次 --在类中
- 方法级(setup_method/teardown_method)开始于方法始末 --在类中
- 类里面的(setup/teardown)运行在调用方法的前后 --在类中 与4的作用一样
import pytest
def setup_module():
print("每个模块开始前执行一次")
def teardown_module():
print("每个模块结束后执行一次")
def setup_function():
print("******方法前开始前执行*******")
def teardown_function():
print("*****方法结束后执行******")
def add(x, y):
return x + y
def test_add_01():
print("***01***")
assert add(3, 4) == 7
def test_add_02():
print("***02***")
assert add(3, 5) == 8
class TestClass():
def setup(self):
print("setup: 每个用例开始前执行")
def teardown(self):
print("teardown: 每个用例结束后执行")
def setup_class(self):
print("setup_class:所有用例执行之前")
def teardown_class(self):
print("teardown_class:所有用例执行之前")
def setup_method(self):
print("setup_method: 每个用例开始前执行")
def teardown_method(self):
print("teardown_method: 每个用例结束后执行")
def test_class_1(self):
print("*****01******")
x = 1
assert 1 == 1
def test_class_2(self):
print("*****01******")
x = 3
assert 3 == "3"
if __name__ == "__main__":
pytest.main(['-s', 'test_class.py'])
7.自定义测试用例的预置条件
fixture
- firture相对于setup和teardown来说应该有以下几点优势
- 命名方式灵活,不局限于setup和teardown这几个命名
- scope=“module” 可以实现多个.py跨文件共享前置, 每一个.py文件调用一次
- scope=“session” 以实现多个.py跨文件使用一个session来完成多个用例
- scope=“function” 默认值,针对函数有效,同setup_function
- scope=“class” 每个类开始前后执行一次,同setup_class
conftest.py
- 上面一个案例是在同一个.py文件中,多个用例调用一个登陆功能,如果有多个.py的文件都需要调用这个登陆功能的话,那就不能把登陆写到用例里面去了。此时应该要有一个配置文件,单独管理一些预置的操作场景,pytest里面默认读取conftest.py里面的配置
- conftest.py配置脚本名称是固定的,不能改名称
- conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
- 不需要import导入 conftest.py,pytest用例会自动查找
yield
- fixture的teardown操作并不是独立的函数,用yield关键字呼唤teardow
- 如果其中一个用例出现异常,不影响yield后面的teardown执行,运行结果互不影响,并且全部用例执行完之后,yield呼唤teardown操作
- 如果在setup就异常了,那么是不会去执行yield后面的teardown内容了
- yield也可以配合with语句使用,可参见官方文档
- 除了yield可以实现teardown,在request-context对象中注册addfinalizer方法也可以实现终结函数
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File : test_fixture.py
# @Author: Nan Luo
# @Date : 2021/6/15 23:55
# @Desc :
import pytest
# 同一目录下的新增conftest.py文件
@pytest.fixture()
def login():
print("****登录系统****")
yield
print("执行teardown操作")
print("*****登出系统******")
def test_01(login):
print("登录后,执行语句01")
assert 1 == 1
def test_02():
print("不需要登录,执行语句2")
assert 2 == 2
def test_03(login):
print("登录后,执行语句3")
if __name__ == '__main__':
pytest.main(['-s', 'test_fixture.py'])
常用断言
pytest里面断言实际上就是python里面的assert断言方法,常用的有以下几种
- assert xx 判断xx为真
- assert not xx 判断xx不为真
- assert a in b 判断b包含a
- assert a== b 判断a等于b
- assert a != b 判断a不等于b
8.测试报告
a. pytest-HTML
- 源码地址:https://github.com/pytest-dev/pytest-html
- 安装方式:pip install pytest-html
- 使用方法:pytest --html==report.html
- 常用方式:pytest --html ==./report/report.html --self-contained-html # 在指定路径生成html报告,并且把样式直接添加在html中,避免在分享报告时丢失样式
- 更多功能:https://github.com/pytest-dev/pytest-html
- 添加截图:实现参考代码
- 失败重跑:pip install pytest-rerunfailures
- 使用方式:pytest --reruns 1 --html ==./report/report.html --self-contained-html
# conftest.py
import pytest
from selenium import webdriver
driver = None
@pytest.fixture()
def login():
print("****登录系统****")
yield
print("执行teardown操作")
print("*****登出系统******")
@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
"""
当测试失败的时候,自动截图,展示到html报告中
:param item:
"""
pytest_html = item.config.pluginmanager.getplugin('html')
outcome = yield
report = outcome.get_result()
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):
file_name = report.nodeid.replace("::", "_")+".png"
screen_img = _capture_screenshot()
if file_name:
html = '<div><img src="https://img-blog.csdnimg.cn/2022010616204883800.png" alt="screenshot" style="width:600px;height:300px;" ' \
'οnclick="window.open(this.src)" align="right"/></div>' % screen_img
extra.append(pytest_html.extras.html(html))
report.extra = extra
def _capture_screenshot():
'''
截图保存为base64,展示到html中
:return:
'''
return driver.get_screenshot_as_base64()
@pytest.fixture(scope='session', autouse=True)
def browser(request):
global driver
if driver is None:
driver = webdriver.Chrome()
def end():
driver.quit()
request.addfinalizer(end)
return driver
# test_01.py
from selenium import webdriver
import time
def test_captrue_01(browser):
browser.get("https://www.baidu.com/")
time.sleep(2)
t = browser.title
assert t == "百度"
# 运行:cmd
# pytest --html=./report/report.html --self-contained-html
# 当前实现已知BUG, 只要是失败就截图,无法精确关联
b. 安装allure-pytest模块
# 安装allure-pytest
pip install allure-pytest -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
- 安装allure命令行工具,下载地址:
- 设置环境变量:把命令行bin目录的路径添加到环境变量中
- 运行用例:cmd 到case目录下执行,pytest --alluredir ./report/allure_raw
- 启动allure服务:allure serve report/allure_raw --在命令行中执行 虚拟环境无法识别
- 参考文档:https://blog.csdn.net/lovedingd/article/details/98952868
- 学习中常见错误处理:
1、https://blog.csdn.net/u013238020/article/details/105905343
–实际处理方式:pip list | grep allure pip uninstall allure
9. 参数化parametrize
pytest.mark.parametrize装饰器可以实现测试用例参数化。
import pytest
# 实现检查一定的输入和期望输出测试功能的典型例子
# 可以标记单个测试实例在参数化,例如使用内置的mark.xfail
@pytest.mark.parametrize("test_input,expected",
[("3+5", 8),
("2+4", 6),
("6 * 9", 42),
pytest.param("6 * 8", 42, marks=pytest.mark.xfail),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
# 参数组合
# 参数设置为x=0/y=2,x=1/y=2,x=0/y=3,x=1/y=3
# 可以用于接口测试的场景组合测试
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
print("测试数据组合:x->%s, y->%s" % (x, y))
if __name__ == "__main__":
pytest.main(["-s", "test_parametrize.py"])
10. pytest.ini 配置文件
放置位置: 工程的根目录下
[pytest]
xfail_strict = true
addopts = -v --reruns 1 --html=report.html --self-contained-html