pytest是python的一种单元测试框架,与python自带的unittest测试框架类似,但是比unittest框架使用起来更简洁,效率更高。根据pytest的官方网站介绍,它具有如下特点:
为什么要学习这个框架,看python鄙视链:pytest 鄙视 > unittest 鄙视 > robotframework 鄙视 > 记流水账 鄙视 > "hello world"小白
# 安装
pip install -U pytest
# 查看版本
pip show pytest / pytest --version
测试文件以test_开头(以_test结尾也可以)
测试类以Test开头,并且不能带有 __init__
方法
测试函数以test_开头
断言使用assert
1.执行某个目录下所有的用例
pytest 文件名/
2.执行某一个py文件下用例
pytest 脚本名称.py
3.-k 按关键字匹配
pytest -k "My TestCase"
这将运行包含与给定字符串表达式匹配的名称的测试,其中包括Python
使用文件名,类名和函数名作为变量的运算符。
4.按节点运行
每个收集的测试都分配了一个唯一的nodeid,它由模块文件名和后跟说明符组成
来自参数化的类名,函数名和参数,由:: characters分隔。
运行.py模块里面的某个函数
pytest test_mod.py::test_func
运行.py模块里面,测试类里面的某个方法
pytest test_mod.py::TestClass::test_method
5.标记表达式
pytest -m smoke
将运行用@pytest.mark.smoke装饰器修饰的所有测试。
import pytest
@pytest.mark.smoke
def test_demo():
pass
6.从包里面运行
pytest --pyargs pkg.testing
这将导入pkg.testing并使用其文件系统位置来查找和运行测试。
7.-x 遇到戳我时停止测试
pytest -x test_class.py
8、-s 关闭捕捉,输出打印信息。
pytest -s test_demo.py
9、-q 安静模式, 不输出环境信息
pytest -q test_demo.py
10、-v:丰富信息模式,输出更详细的用例执行信息
pytest -v test_demo.py
11、-lf:是–last-failed的缩写, 重新运行上次运行失败的用例(或如果没有失败的话会全部跑)
pytest -lf tet_demo.py
12、-ff: 是–failed-first的缩写,会先运行失败的用例在执行其他用例(可能会重新执行,会导致重复运行fixture、setup/teardown)
pytest -ff test_demo.py
13、–setup-show:可以查看fixture的向后顺序
pytest --setup-show
学过unittest的都知道里面用前置和后置setup和teardown非常好用,在每次用例开始前和结束后都去执行一次。
当然还有更高级一点的setupClass和teardownClass,需配合@classmethod装饰器一起使用,在做selenium自动化的时候,它的效率尤为突出,可以只启动一次浏览器执行多个用例。
pytest框架也有类似于setup和teardown的语法,并且还不止这四个
模块级(setup_module/teardown_module)开始于模块始末,全局的
函数级(setup_function/teardown_function)只对函数用例生效(不在类中)
类级(setup_class/teardown_class)只在类中前后运行一次(在类中)
方法级(setup_method/teardown_method)开始于方法始末(在类中)
类里面的(setup/teardown)运行在调用方法的前后
#test_fixtclass.py
# coding:utf-8
import pytest
# 类和方法
class TestCase():
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_one(self):
print("正在执行----test_one")
x = "this"
assert 'h' in x
def test_two(self):
print("正在执行----test_two")
x = "hello"
assert hasattr(x, 'check')
def test_three(self):
print("正在执行----test_three")
a = "hello"
b = "hello world"
assert a in b
if __name__ == "__main__":
pytest.main(["-s", "test_fixtclass.py"])
运行结果
test_fixtclass.py setup_class:所有用例执行之前
setup_method: 每个用例开始前执行
setup: 每个用例开始前执行
正在执行----test_one
.teardown: 每个用例结束后执行
teardown_method: 每个用例结束后执行
setup_method: 每个用例开始前执行
setup: 每个用例开始前执行
正在执行----test_two
Fteardown: 每个用例结束后执行
teardown_method: 每个用例结束后执行
setup_method: 每个用例开始前执行
setup: 每个用例开始前执行
正在执行----test_three
.teardown: 每个用例结束后执行
teardown_method: 每个用例结束后执行
teardown_class:所有用例执行之前
运行的优先级:setup_class》setup_method》setup 》用例》teardown》teardown_method》teardown_class
备注:这里setup_method和teardown_method的功能和setup/teardown功能是一样的,一般二者用其中一个即可
firture相对于setup和teardown来说应该有以下几点优势
fixture(scope="function", params=None, autouse=False, ids=None, name=None):
"""使用装饰器标记fixture的功能
可以使用此装饰器(带或不带参数)来定义fixture功能。 fixture功能的名称可以在以后使用
引用它会在运行测试之前调用它:test模块或类可以使用pytest.mark.usefixtures(fixturename标记,
测试功能可以直接使用fixture名称作为输入参数,在这种情况下,夹具实例从fixture返回功能将被注入。
:arg scope: scope 有四个级别参数 "function" (默认), "class", "module" or "session".
:arg params: 一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它
:arg autouse: 如果为True,则为所有测试激活fixture func 可以看到它。 如果为False(默认值)则显式需要参考来激活fixture
:arg ids: 每个字符串id的列表,每个字符串对应于params 这样他们就是测试ID的一部分。 如果没有提供ID它们将从params自动生成
:arg name: fixture的名称。 这默认为装饰函数的名称。 如果fixture在定义它的同一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽; 解决这个问题的一种方法是将装饰函数命名“fixture_ ”然后使用”@ pytest.fixture(name ='')""" 。
示例
# 新建一个文件test_demo.py
# coding:utf-8
import pytest
# 不带参数时默认scope="function"
@pytest.fixture()
def login():
print("输入账号,密码先登录")
def test_1(login):
print("用例1:登录之后执行1")
def test_2(): # 不传login
print("用例2:不需要登录执行2")
def test_3(login):
print("用例3:登录之后执行3")
if __name__ == "__main__":
pytest.main(["-s", "test_demo.py"])
运行结果
test_demo.py 输入账号,密码先登录
用例1:登录之后执行1
.用例2:不需要登录执行2
.输入账号,密码先登录
用例3:登录之后执3
.
上面一个例子是在同一个.py文件中,多个用例调用一个登陆功能,如果有多个.py的文件都需要调用登陆功能的话,那就不能把登陆写到用例里。而pytest有一个配置文件conftest.py,可以管理这些预置的操作场景,pytest会默认读取conftest.py里面的配置。
conftest.py配置需要注意以下点:
__init__.py
conftest.py
# coding:utf-8
import pytest
@pytest.fixture()
def login():
print("输入账号,密码先登录")
test_demo_1.py
# coding:utf-8
import pytest
def test_1(login):
print("用例1:登录之后执行1")
def test_2(): # 不传login
print("用例2:不需要登录执行2")
def test_3(login):
print("用例3:登录之后执行3")
if __name__ == "__main__":
pytest.main(["-s", "test_demo_1.py"])
test_demo_2.py
# coding:utf-8
import pytest
def test_4(login):
print("用例4:登录之后执行1")
def test_5(): # 不传login
print("用例5:登录之后执行3")
if __name__ == "__main__":
pytest.main(["-s", "test_demo_2.py"])
scope 有四个级别参数 “function” (默认), “class”, “module” or "session"的区别
代码示例
# conftest.py
import pytest
# 作用域 function
@pytest.fixture(scope='function')
def fix_func():
print('\n方法级:fix_func...')
# 作用域 class
@pytest.fixture(scope='class')
def fix_class():
print('\n类级:fix_class...')
# 作用域 module
@pytest.fixture(scope='module')
def fix_module():
print('\n模块级:fix_module...')
# 作用域 session
@pytest.fixture(scope='session')
def fix_session():
print('\n会话级:fix_session...')
# test_demo_1.py
import pytest
class TestClass_1:
"""测试类1"""
@pytest.mark.usefixtures('fix_func')
@pytest.mark.usefixtures('fix_class')
@pytest.mark.usefixtures('fix_module')
@pytest.mark.usefixtures('fix_session')
def test_1(self):
print("测试用例1")
@pytest.mark.usefixtures('fix_func')
@pytest.mark.usefixtures('fix_class')
@pytest.mark.usefixtures('fix_module')
@pytest.mark.usefixtures('fix_session')
def test_2(self):
print("测试用例2")
class TestClass_2:
"""测试类2"""
@pytest.mark.usefixtures('fix_func')
@pytest.mark.usefixtures('fix_class')
@pytest.mark.usefixtures('fix_module')
@pytest.mark.usefixtures('fix_session')
def test3(self):
print("测试用例3")
if __name__ == '__main__':
pytest.main(['-s'])
运行结果
collected 3 items
test_demo_1.py
会话级:fix_session...
模块级:fix_module...
类级:fix_class...
方法级:fix_func...
测试用例1
.
方法级:fix_func...
测试用例2
.
类级:fix_class...
方法级:fix_func...
测试用例3
.
============================== 3 passed in 0.02s ===============================
在测试类使用function级别的fixture示例
# test_demo.py
import pytest
@pytest.mark.usefixtures('fix_func') # conftest.py 还是使用上个示例的
class TestCase:
def test_1(self):
print("测试用例1")
def test_2(self):
print("测试用例2")
if __name__ == '__main__':
pytest.main(['-s'])
运行结果:
collected 2 items
test_demo.py
方法级:fix_func...
测试用例1
.
方法级:fix_func...
测试用例2
.
============================== 2 passed in 0.01s ===============================
1.Fixtures可以使用yield 或者 return 语句为测试函数提供它们的值,当使用yield语句,在它之后的代码块作为拆卸代码执行,而且不管测试结果如何,fixture功能必须只产生一次。
2.通过上面示例可以看到fixture通过scope参数控制setup级别作为用例之前的操作,用例执行完之后肯定也会有teardown操作。而pytest是用yield关键字呼唤teardown操作。
示例代码:
# conftest.py
import pytest
@pytest.fixture(scope='function')
def fix_fun2():
print("\n测试用例执行前,执行fix_fun2前置操作")
n = 1
yield n
print("测试用例执行前,执行fix_fun2后置操作")
# test_demo_3.py
import pytest
@pytest.mark.usefixtures("fix_fun2")
class TestDemo2:
def test_fun4(self, fix_fun2):
print("测试用例4")
print(f'yield 返回值:{fix_fun2}')
if __name__ == '__main__':
pytest.main(['-s'])
运行结果:
test_demo_3.py
测试用例执行前,执行fix_fun2前置操作
测试用例4
yield 返回值:1
.测试用例执行前,执行fix_fun2后置操作
============================== 1 passed in 0.01s ===============================
前面讲过,fixture 可以同时使用多个,且 fixture 间可以相互调用
在上个代码示例基础上我们改一下:
# conftest.py
import pytest
@pytest.fixture(scope='function')
def fix_fun2():
print("\n测试用例执行前,执行fix_fun2前置操作")
yield ""
print("测试用例执行前,执行fix_fun2后置操作")
@pytest.fixture()
def fix_fun3(fix_fun2): # 调用 fix_fun2
print("\n测试用例执行前,执行fix_fun3前置操作")
yield fix_fun2 # 把fix_fun2 作为返回值
print("测试用例执行前,执行fix_fun3后置操作")
@pytest.fixture()
def fix_fun4():
print("\n测试用例执行前,执行fix_fun4前置操作")
yield ""
print("测试用例执行前,执行fix_fun4后置操作")
# test_demo_3.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/21 12:43 下午
# @Author : Yio
import pytest
@pytest.mark.usefixtures("fix_fun3")
@pytest.mark.usefixtures("fix_fun4")
class TestDemo2:
def test_fun4(self, fix_fun3, fix_fun4):
print("测试用例4")
print(f'yield 返回值:{fix_fun3}, {fix_fun4}')
if __name__ == '__main__':
pytest.main(['-s'])
运行结果:
collected 1 item
test_demo_3.py
测试用例执行前,执行fix_fun4前置操作
测试用例执行前,执行fix_fun2前置操作
测试用例执行前,执行fix_fun3前置操作
测试用例4
yield 返回值:,
.测试用例执行前,执行fix_fun3后置操作
测试用例执行前,执行fix_fun2后置操作
测试用例执行前,执行fix_fun4后置操作
============================== 1 passed in 0.02s ===============================
从上个示例的运行结果来看,fixture的执行顺序是有它的一套规则的。
当一个测试方法同时调用多个fixture时,会根据各fixture作用域、依赖关系、传参顺序来确定fixture的执行顺序。
执行顺序要分两种情况:
我们通过示例来看:
# conftest.py
import pytest
@pytest.fixture
def fix_func():
print('\n方法级-被调用方...')
yield
print('\n方法级-被调用方...')
@pytest.fixture
def fix_func_param_1(fix_func):
print('\n方法级-传参法-1...')
yield
print('\n方法级-传参法-1...')
@pytest.fixture
def fix_func_param_2():
print('\n方法级-传参法-2...')
yield
print('\n方法级-传参法-2...')
@pytest.fixture
def fix_func_param_3():
print('\n方法级-传参法-3...')
yield
print('\n方法级-传参法-3...')
@pytest.fixture
def fix_func_decorator_1(fix_func):
print('\n方法级-装饰器-1...')
yield
print('\n方法级-装饰器-1...')
@pytest.fixture
def fix_func_decorator_2():
print('\n方法级-装饰器-2...')
yield
print('\n方法级-装饰器-2...')
@pytest.fixture
def fix_func_decorator_3():
print('\n方法级-装饰器-3...')
yield
print('\n方法级-装饰器-3...')
# 作用域 class
@pytest.fixture(scope='class')
def fix_class():
print('\n类级:fix_class...')
yield
print('\n类级:fix_class...')
# 作用域 module
@pytest.fixture(scope='module')
def fix_module():
print('\n模块级:fix_module...')
yield
print('\n模块级:fix_module...')
# 作用域 session
@pytest.fixture(scope='session')
def fix_session():
print('\n会话级:fix_session...')
yield
print('\n会话级:fix_session...')
# test_demo_3.py
import pytest
class TestClass:
"""测试类1"""
@pytest.mark.usefixtures('fix_class')
@pytest.mark.usefixtures('fix_session')
@pytest.mark.usefixtures('fix_func_decorator_3')
@pytest.mark.usefixtures('fix_func_decorator_2')
@pytest.mark.usefixtures('fix_func_decorator_1')
def test_func(self, fix_func_param_1, fix_func_param_2, fix_func_param_3, fix_module):
print('==============>>>执行测试用例<<<==============')
if __name__ == '__main__':
pytest.main(['-s'])
运行结果:
collected 1 item
test_demo_3.py
会话级:fix_session...
模块级:fix_module...
类级:fix_class...
方法级-被调用方...
方法级-装饰器-1...
方法级-装饰器-2...
方法级-装饰器-3...
方法级-传参法-1...
方法级-传参法-2...
方法级-传参法-3...
==============>>>执行测试用例<<<==============
.
方法级-传参法-3...
方法级-传参法-2...
方法级-传参法-1...
方法级-装饰器-3...
方法级-装饰器-2...
方法级-装饰器-1...
方法级-被调用方...
类级:fix_class...
模块级:fix_module...
============================== 1 passed in 0.02s ===============================
c.fixture 的 autouse 参数:
autouse 参数的作用是 自动执行,它会在相应的作用域中不需要调度,自动执行,并且在同一作用域中优先级最高;
# conftest.py
import pytest
# 增加一个 fix_auto 夹具
@pytest.fixture(autouse=True)
def fix_auto():
print('\n方法级-自动执行...')
yield
print('\n方法级-自动执行...')
# test_demo_3.py
import pytest
class TestClass:
"""测试类1"""
@pytest.mark.usefixtures('fix_class')
@pytest.mark.usefixtures('fix_session')
@pytest.mark.usefixtures('fix_func_decorator_1')
def test_func(self, fix_func_param_1, fix_module):
print('==============>>>执行测试用例<<<==============')
运行结果
============================= test session starts =============================
test_demo_3.py::TestClass::test_func
会话级:fix_session...
模块级:fix_module...
类级:fix_class...
方法级-自动执行...
方法级-被调用方...
方法级-装饰器-1...
方法级-传参法-1...
==============>>>执行测试用例<<<==============
PASSED
方法级-传参法-1...
方法级-装饰器-1...
方法级-被调用方...
方法级-自动执行...
类级:fix_class...
模块级:fix_module...
会话级:fix_session...
============================== 1 passed in 0.03s ==============================
@pytest.fixture装饰器,传参就用默认的request参数, request.param 这是接收传入的参数,添加indirect=True参数是为了把fixture当成一个函数去执行,而不是一个参数。
示例:
一个参数的用法
# test_demo.py
import pytest
# 测试账号数据
test_user_data = ["admin1", "admin2"]
@pytest.fixture(scope="module")
def login(request):
user = request.param
print("登录账户:%s"%user)
return user
@pytest.mark.parametrize("login", test_user_data, indirect=True)
def test_login(login):
'''登录用例'''
a = login
print("测试用例中login的返回值:%s" % a)
assert a != ""
if __name__ == "__main__":
pytest.main(["-s"])
多个参数的用法
# test_demo.py
import pytest
test_user_data = [{
"user": "Tom", "pwd": "111111"},
{
"user": "Vicky", "pwd": ""}]
@pytest.fixture(scope="module")
def login(request):
user = request.param["user"]
pwd = request.param["pwd"]
print(f"登录账户:{user}")
print(f"登录密码:{pwd}")
if pwd:
return True
else:
return False
# indirect=True 声明login是个函数
@pytest.mark.parametrize("login", test_user_data, indirect=True)
def test_login(login):
a = login
print("测试用例中login的返回值:%s" % a)
assert a, "失败原因:密码为空"
if __name__ == '__main__':
pytest.main(['-rs'])
fixture里面有个参数autouse,默认是Fasle没开启的,设置为True开启自动使用该fixture功能,这样用例就不用每次都去传参,会自动按照fixture的执行顺序去执行。
示例:
import pytest
@pytest.fixture(scope='function', autouse=True)
def open_browse(request):
print('function:打开浏览器!')
yield
print('function:关闭浏览器!')
@pytest.fixture(scope='class', autouse=True)
def open_home_page():
print('class:打开主页!')
class TestO1:
def test_o1(self):
print('开始执行用例1')
def test_02(self):
print('开始执行用例2')
class Test02:
def test_o3(self):
print('开始执行用例3')
def test_04(self):
print('开始执行用例4')
if __name__ == '__main__':
pytest.main(['-s'])
运行结果:
collected 4 items
test_demo.py class:打开主页!
function:打开浏览器!
开始执行用例1
.function:关闭浏览器!
function:打开浏览器!
开始执行用例2
.function:关闭浏览器!
class:打开主页!
function:打开浏览器!
开始执行用例3
.function:关闭浏览器!
function:打开浏览器!
开始执行用例4
.function:关闭浏览器!
============================== 4 passed in 0.01s ===============================
pytest 运行完用例之后会生成 .pytest_cache 缓存文件夹,用于记录用例的ids和上一次失败的用例。方便我们使用cache
参数说明:
--last-failed
仅运行上一次失败的用例--failed-first
运行全部的用例,但是上一次失败的用例先运行--new-first
根据文件插件的时间,新的测试用例会先运行Pytest运行完成后,会在当前的目录生成一个 .pytest_cache
的缓存文件夹,层级结构如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rmeldc1G-1608128106379)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201214205804243.png)]
这是我上一次执行的结果:
=========================== short test summary info ============================
FAILED test_demo_1.py::TestO1::test_01 - assert False
========================= 1 failed, 2 passed in 1.07s ==========================
上面fixture的参数介绍的时候,有提到name的参数说明,实际上它的作用是给fixture设置一个别名
name: fixture的名称。 这默认为装饰函数的名称。 如果fixture在定义它的同一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽; 解决这个问题的一种方法是将装饰函数命名“fixture_ ”然后使用”@ pytest.fixture(name ='')"""
现在我们来演示一下它的作用
# test_demo.py
import pytest
class TestO1:
@pytest.fixture(name='driver')
def login(self):
print('执行前')
yield
print('执行后')
def test_01(self, driver):
"""测试用例1执行了"""
assert 2 < 1
if __name__ == '__main__':
pytest.main(["-s"])
运行结果:
ollecting ... collected 1 item
test_demo_1.py::TestO1::test_01 执行前
FAILED [100%]
test_demo_1.py:14 (TestO1.test_01)
self = , driver = None
def test_01(self, driver):
"""测试用例1执行了"""
> assert 2 < 1
E assert 2 < 1
test_demo_1.py:17: AssertionError
执行后
断言失败
=================================== FAILURES ===================================
________________________________ TestO1.test_01 ________________________________
self = , driver = None
def test_01(self, driver):
"""测试用例1执行了"""
> assert 2 < 1
E assert 2 < 1
test_demo_1.py:17: AssertionError
---------------------------- Captured stdout setup -----------------------------
执行前
--------------------------- Captured stdout teardown ---------------------------
执行后
=========================== short test summary info ============================
FAILED test_demo_1.py::TestO1::test_01 - assert 2 < 1
============================== 1 failed in 0.06s ===============================
进程已结束,退出代码1
pytest的优秀之处前面已经提过,它有非常多三方插件支持和自定义开发插件。其中一个插件pytest-HTML,是用于生成测试结果的HTML报告。
【https://github.com/pytest-dev/pytest-html】
pip3 install pytest-html
pytest --html=report.html
# 直接执行"pytest --html=report.html"生成的报告会在当前脚本的同一路径,如果想指定报告的存放位置,放到当前脚本的同一目录下的report文件夹里
pytest --html=./report/report.html
我这里就不再具体去描述这个插件,因为我们有更好、更强大的选择-Allure,后面会说到。
pytest-xdist插件扩展了一些独特的测试执行模式pytest:
GitHub地址:https://github.com/pytest-dev/pytest-xdist
分布式实现的原理:https://github.com/pytest-dev/pytest-xdist/blob/master/OVERVIEW.md
pip install pytest-xdist # 安装
pip show pytest-xdist # 查看版本
多cpu并行执行用例,直接加个-n参数即可,后面num参数就是并行数量,比如num设置为2
pytest -n 2
pytest.main(['-s', '-n 2']) # pytest.main 函数用法
pip3 install pytest-repeat
–count=2 或者 --count 2
pytest --count=2
如果需要验证偶现问题,可以一次又一次地运行相同的测试直到失败,这个插件将很有用
可以将pytest的 -x 选项与pytest-repeat结合使用,以强制测试运行程序在第一次失败时停止
pytest -x --count 1000
如果要在代码中将某些测试用例标记为执行重复多次,可以使用 @pytest.mark.repeat(count)
@pytest.mark.repeat(2)
def test_01(self):
print('开始执行用例1')
命令行参数
**作用:**可以覆盖默认的测试用例执行顺序,类似fixture的scope参数
示例:
# test_demo.py
import pytest
class TestO1:
def test_o1(self):
print('开始执行用例1')
def test_02(self):
print('开始执行用例2')
class Test02:
def test_o3(self):
print('开始执行用例3')
def test_04(self):
print('开始执行用例4')
if __name__ == '__main__':
pytest.main(['-v', '--count=2', '-repeat-scope=class'])
运行结果:
plugins: xdist-2.1.0, repeat-0.9.1, forked-1.3.0
collecting ... collected 8 items
test_demo.py::TestO1::test_o1[1-2] PASSED [ 12%]
test_demo.py::TestO1::test_o1[2-2] PASSED [ 25%]
test_demo.py::TestO1::test_02[1-2] PASSED [ 37%]
test_demo.py::TestO1::test_02[2-2] PASSED [ 50%]
test_demo.py::Test02::test_o3[1-2] PASSED [ 62%]
test_demo.py::Test02::test_o3[2-2] PASSED [ 75%]
test_demo.py::Test02::test_04[1-2] PASSED [ 87%]
test_demo.py::Test02::test_04[2-2] PASSED [100%]
============================== 8 passed in 0.05s ===============================
pytest-repeat不能与unittest.TestCase测试类一起使用。无论–count设置多少,这些测试始终仅运行一次,并显示警告
当测试执行遇到失败或错误时能及时看到详细的堆栈信息
pip install pytest-instafail
示例:
# test_demo.py
import pytest
from time import sleep
class TestO1:
@pytest.fixture(autouse=True)
def login(self):
print('执行前')
yield
print('执行后')
@pytest.mark.usefixtures('login')
def test_01(self, login):
"""测试用例1执行了"""
assert 2 < 1
def test_02(self):
pass
def test_03(self):
sleep(1)
if __name__ == '__main__':
pytest.main(["--tb=line", "--instafail"]) # 和--tb=line效果更好
运行结果:
collecting ... collected 3 items
test_demo_1.py::TestO1::test_01 执行前
FAILED [ 33%]
test_demo_1.py:14 (TestO1.test_01)
self = .test_demo_1.TestO1 object at 0x10ed34a10>, login = None
@pytest.mark.usefixtures('login')
def test_01(self, login):
"""测试用例1执行了"""
> assert 2 < 1
E assert 2 < 1
test_demo_1.py:18: AssertionError
执行后
执行前
PASSED [ 66%]执行后
执行前
PASSED [100%]执行后
断言失败
test_demo_1.py::TestO1::test_02
test_demo_1.py::TestO1::test_03
=================================== FAILURES ===================================
________________________________ TestO1.test_01 ________________________________
self = .test_demo_1.TestO1 object at 0x10ed34a10>, login = None
@pytest.mark.usefixtures('login')
def test_01(self, login):
"""测试用例1执行了"""
> assert 2 < 1
E assert 2 < 1
test_demo_1.py:18: AssertionError
---------------------------- Captured stdout setup -----------------------------
执行前
--------------------------- Captured stdout teardown ---------------------------
执行后
=========================== short test summary info ============================
FAILED test_demo_1.py::TestO1::test_01 - assert 2 < 1
========================= 1 failed, 2 passed in 1.08s ==========================
进程已结束,退出代码1
unittest有ddt,pytest当然也有他们的参数化parametrize
# 一个参数的时候
@pytest.mark.parametrize(“参数名”,列表数据)
# 多个参数的时候
@pytest.mark.parametrize(“参数名1,参数名2”,列表嵌套元组数据/列表嵌套字典)
示例
# test_demo.py
import pytest
@pytest.mark.parametrize("test_input,expected",
[("3+5", 8),
("2+4", 6),
("6 * 9", 42),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
if __name__ == '__main__':
pytest.main(['-s'])
运行结果
collected 3 items
test_demo.py ..F
=================================== FAILURES ===================================
_____________________________ test_eval[6 * 9-42] ______________________________
test_input = '6 * 9', expected = 42
@pytest.mark.parametrize("test_input,expected",
[("3+5", 8),
("2+4", 6),
("6 * 9", 42),
])
def test_eval(test_input, expected):
> assert eval(test_input) == expected
E AssertionError: assert 54 == 42
E + where 54 = eval('6 * 9')
test_demo.py:12: AssertionError
=========================== short test summary info ============================
FAILED test_demo.py::test_eval[6 * 9-42] - AssertionError: assert 54 == 42
========================= 1 failed, 2 passed in 0.05s ==========================
标记单个测试实例在参数化,例如使用内置的mark.xfail
# test_demo.py
import pytest
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
pytest.param("6 * 9", 42, marks=pytest.mark.xfail),
])
def test_eval(test_input, expected):
print("-------开始用例------")
assert eval(test_input) == expected
if __name__ == '__main__':
pytest.main(['-s'])
运行结果
collected 3 items
test_demo.py -------开始用例------
.-------开始用例------
.-------开始用例------
x
========================= 2 passed, 1 xfailed in 0.05s =========================
1.若要获得多个参数化参数的所有组合,可以堆叠参数化装饰器
# test_demo.py
import pytest
@pytest.mark.parametrize('x', [1, 3])
@pytest.mark.parametrize('y', [1, 2, 3])
def test_demo(x, y):
print(f'\n x:{x} y:{y}')
if __name__ == '__main__':
pytest.main(['-s'])
运行结果
collected 6 items
test_demo.py
x:1 y:1
.
x:3 y:1
.
x:1 y:2
.
x:3 y:2
.
x:1 y:3
.
x:3 y:3
.
============================== 6 passed in 0.01s ===============================
ids用来传递测试用例的序列,可在控制台打印出来,也可以结果allure在测试套件中显示
示例
# test_demo.py
import pytest
test_data = [{
'a': 1, 'b': 2}, {
'a': 2, 'b': 3}] # 列表嵌套字典的方式,在做接口自动化的时候比较常见
test_case_id = ['case 001', 'case 002']
@pytest.mark.parametrize('x', test_data, ids=test_case_id)
def test_demo(x):
assert x['a'] * x['b'] > 1
if __name__ == '__main__':
pytest.main(['-v'])
运行结果:
collecting ... collected 2 items
test_demo.py::test_demo[case 001] PASSED [ 50%]
test_demo.py::test_demo[case 002] PASSED [100%]
============================== 2 passed in 0.01s ===============================
pytest里面断言实际上就是python里面的assert断言方法,常用的有以下几种
这是python的基础语法,就不详细说了
import pytest
def test_demo():
a = 'test'
b = 't'
assert b not in a, f"判断b:{b} in a:{a}" # 这里可以加入说明,在断言失败的时候,会打印
if __name__ == '__main__':
pytest.main(['-v'])
运行结果:
collected 1 item
test_demo.py F
=================================== FAILURES ===================================
__________________________________ test_demo ___________________________________
def test_demo():
a = 'test'
b = 't'
> assert b not in a, f"判断b:{b} in a:{a}"
E AssertionError: 判断b:t in a:test
E assert 't' not in 'test'
E 't' is contained here:
E test
E ? +
test_demo.py:9: AssertionError
=========================== short test summary info ============================
FAILED test_demo.py::test_demo - AssertionError: 判断b:t in a:test
============================== 1 failed in 0.05s ===============================
可以标记无法在某些平台上运行的测试功能,或者您希望失败的测试功能
skip意味着只有在满足某些条件时才希望测试通过,否则pytest应该跳过运行测试。 常见示例是在非Windows平台上跳过仅限Windows的测试,或跳过测试依赖于当前不可用的外部资源(例如数据库)。
xfail意味着您希望测试由于某种原因而失败。 一个常见的例子是对功能的测试尚未实施,或尚未修复的错误。 当测试通过时尽管预计会失败(标有pytest.mark.xfail),它是一个xpass,将在测试摘要中报告。
pytest计数并分别列出skip和xfail测试。 未显示有关跳过/ xfailed测试的详细信息默认情况下,以避免混乱输出。 您可以使用-r选项查看与“short”字母对应的详细信息显示在测试进度中
pytest -rxXs # show extra info on xfailed, xpassed, and skipped tests
有关-r选项的更多详细信息,请运行pytest -h
示例:
# test_demo.py
import pytest
@pytest.mark.skip(reason='跳过次用例,不执行!')
def test_demo():
pass
def test_demo_1():
pass
if __name__ == '__main__':
pytest.main(['-rs'])
运行结果:
collected 2 items
test_demo.py s. [100%]
=========================== short test summary info ============================
SKIPPED [1] test_demo.py:6: 跳过次用例,不执行!
========================= 1 passed, 1 skipped in 0.01s =========================
如果你希望有条件的跳过测试用例,你可以改用skipif
示例:
import pytest
import sys
# 你希望测试用例在python 3.6以下的宝宝运行
@pytest.mark.skipif(sys.version_info > (3, 6), reason="请在python 3.6 以下的版本运行")
def test_function():
pass
if __name__ == '__main__':
pytest.main(['-rs'])
运行结果:
============================= test session starts ==============================
platform darwin -- Python 3.7.7, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: /Users/***/PycharmProjects/Demo
collected 1 item
test_demo.py s [100%]
=========================== short test summary info ============================
SKIPPED [1] test_demo.py:8: 请在python 3.6 以下的版本运行
============================== 1 skipped in 0.01s ==============================
您可以在模块之间共享skipif标记。参考以下官方文档案例
# content of test_mymodule.py
import mymodule
minversion = pytest.mark.skipif(mymodule.__versioninfo__ < (1,1),
reason="at least mymodule-1.1 required")
@minversion
def test_function():
...
# test_myothermodule.py
from test_mymodule import minversion
@minversion
def test_anotherfunction():
警告:强烈建议不要在使用继承的类上使用skipif。 pytest中的一个已知错误标记可能会导致超类中的意外行为。
pytest里面用xfail标记用例为失败的用例,会直接跳过后续代码的执行,所以当用例a失败的时候,如果用例b依赖于用例a的结果,那可以直接跳过用例b的后续测试,直接给他标记失败xfail。
示例
# test_demo.py
import pytest
test_data = ["Tom", ""]
@pytest.fixture(scope="module")
def login(request):
user = request.param
print(f"登录账户:{user}")
if user:
return True
else:
return False
# indirect=True 声明login是个函数
@pytest.mark.parametrize("login", test_data, indirect=True)
def test_login(login):
a = login
if not a:
pytest.xfail('用户名不存在,登录失败')
print("测试用例中login的返回值:%s" % a)
assert a, "失败原因:用户名为空"
if __name__ == '__main__':
pytest.main(['-s'])
运行结果:
collected 2 items
test_demo.py 登录账户:Tom
测试用例中login的返回值:True
.登录账户:
x
========================= 1 passed, 1 xfailed in 0.05s =========================
pytest配置文件可以改变pytest的运行方式,它是一个固定的文件pytest.ini文件,读取配置信息,按指定的方式去运行。
pytest里面有些文件是非test文件
常用的配置有下面的
2.设置xfail_strict = true可以让那些标记为@pytest.mark.xfail但实际通过的测试用例被报告为失败
3.markers 有时候mark标签多了,不容易记住,为了方便后续执行指令的时候能准确使用mark的标签,可以写入到pytest.ini文件
# 保存为pytest.ini文件内的基本格式
[pytest]
addopts = -rsxX
xfail_strict = true
markers =
smoke: 执行冒烟级别的用例
P1: 执行主功能级别的用例
pytet --markers
运行结果:
@pytest.mark.smoke: 执行冒烟级别的用例
@pytest.mark.P1: 执行主功能级别的用例
@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/stable/warnings.html#pytest-mark-filterwarnings
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
先上个图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vtbuI37y-1608128106381)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201204191501617.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yXvVuD5g-1608128106382)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201204194835274.png)]
官方介绍:
个人见解:
容易部署、美观、领导喜欢、能和Jenkins结合使用
pip3 install allure-pytest
通过–alluredir=allure报告生成文件夹路径,在pytest.mian中加入
在运行的时候加入终端执行的代码,就会自动在浏览器打开allure的报告展示
其中参数:–clean-alluredir 每次生成allure报告的时候会清除上一次报告生成的数据;
os.system("allure generate -o Report/Allure/")
os.system("allure serve Report/Allure/")
示例:
# run.py
import os
import pytest
if __name__ == '__main__':
pytest.main(["-s", "-v", "--alluredir=Report/Allure/AllureResult"])
os.system("allure generate -o Report/Allure/AllureResult")
os.system("allure serve Report/Allure/AllureResult")
可以理解成环境变量参数,没有什么实际作用,只是为了让别人知道本次测试的运行环境参数而已,显示啥都是自己定的,默认是没有的
在生产allure报告目录下,创建environment.properties或者environment.xml文件,注意内容不能有中文
这里就environment.properties示例
Browser=Chrome
Browser.Version=81
Stand=Production
ApiUrl=127.0.0.1
python.Version=3.7
生成报告显示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yeXjvREu-1608128106384)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201208221127489.png)]
示例:
# test_demo.py
import pytest
import allure
import os
@pytest.fixture(scope='function', autouse=True)
def open_browse(request):
print('function:打开浏览器!')
yield
print('function:关闭浏览器!')
@pytest.fixture(scope='class', autouse=True)
def open_home_page():
print('class:打开主页!')
@allure.step('步骤1')
def run_step_1():
pass
@allure.step('步骤2')
def run_step_2():
pass
class TestO1:
def test_o1(self):
run_step_1()
run_step_2()
def test_02(self):
print('开始执行用例2')
class Test02:
def test_o3(self):
print('开始执行用例3')
def test_04(self):
print('开始执行用例4')
if __name__ == '__main__':
pytest.main(["-s", "-v", "--alluredir=Report/Allure/AllureResult"])
os.system("allure generate -o Report/Allure/AllureResult")
os.system("allure serve Report/Allure/AllureResult")
运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hiDfLgSC-1608128106385)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201208222915747.png)]
**作用:**allure报告还支持显示许多不同类型的附件,可以补充测试结果;自己想输出啥就输出啥,挺好的
语法: allure.attach(body, name, attachment_type, extension)
这个用法是allure根据我们传入的body和文件名name生成一个附件上传到报告上
参数列表
body:要显示在allure上附件里展示的内容
name:附件名字
attachment_type:附件类型,是 allure.attachment_type 里面的其中一种
extension:附件的扩展名(比较少用)
attach还有另外一个用法:allure.attach.file
语法:allure.attach.file(source, name, attachment_type, extension)
而这个用法是把我们本地的附件上传到报告上
allure.attachment_type提供的类型有:
TEXT = ("text/plain", "txt")
CSV = ("text/csv", "csv")
TSV = ("text/tab-separated-values", "tsv")
URI_LIST = ("text/uri-list", "uri")
HTML = ("text/html", "html")
XML = ("application/xml", "xml")
JSON = ("application/json", "json")
YAML = ("application/yaml", "yaml")
PCAP = ("application/vnd.tcpdump.pcap", "pacp")
PDF = ("application/pdf", "pdf")
PNG = ("image/png", "png")
JPG = ("image/jpg", "jpg")
SVG = ("image/svg-xml", "svg")
GIF = ("image/gif", "gif")
BMP = ("image/bmp", "bmp")
TIFF = ("image/tiff", "tiff")
MP4 = ("image/mp4", "mp4")
OGG = ("image/ogg", "ogg")
WEBM = ("image/webm", "webm")
示例:
# test_demo.py
import pytest
import allure
import os
@pytest.fixture(scope='function', autouse=True)
def open_browse(request):
print('function:打开浏览器!')
yield
print('function:关闭浏览器!')
@pytest.fixture(scope='class', autouse=True)
def open_home_page():
print('class:打开主页!')
@allure.step('步骤1')
def run_step_1():
allure.attach("这是一个测试txt附件", 'test_data_1.text', allure.attachment_type.TEXT)
allure.attach.file('./Files/test_data.txt', 'test_data.txt', attachment_type=allure.attachment_type.TEXT)
@allure.step('步骤2')
def run_step_2():
pass
class TestO1:
def test_o1(self):
run_step_1()
run_step_2()
def test_02(self):
print('开始执行用例2')
class Test02:
def test_o3(self):
print('开始执行用例3')
def test_04(self):
print('开始执行用例4')
if __name__ == '__main__':
pytest.main(["-s", "-v", "--alluredir=Report/Allure/AllureResult"])
os.system("allure generate -o Report/Allure/AllureResult")
os.system("allure serve Report/Allure/AllureResult")
报告效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bGjNsAXS-1608128106386)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201209115059978.png)]
语法有三种
注意:前面两种的效果和作用是一致的,我更喜欢用第二种,比较方便
示例:
# test_demo.py
import pytest
import allure
import os
@pytest.fixture(scope='function', autouse=True)
def open_browse(request):
print('function:打开浏览器!')
yield
print('function:关闭浏览器!')
@pytest.fixture(scope='class', autouse=True)
def open_home_page():
print('class:打开主页!')
@allure.step('步骤1')
def run_step_1():
allure.attach("这是一个测试txt附件", 'test_data_1.text', allure.attachment_type.TEXT)
allure.attach.file('./Files/test_data.txt', 'test_data.txt', attachment_type=allure.attachment_type.TEXT)
@allure.step('步骤2')
def run_step_2():
pass
class TestO1:
@allure.description("测试用例1执行了")
def test_o1(self):
run_step_1()
def test_02(self):
"""测试用例2执行了"""
print('开始执行用例2')
class Test02:
@allure.description_html("""
测试用例3的html说明
Firstname
Lastname
William
Smith
""")
def test_o3(self):
pass
def test_04(self):
pass
if __name__ == '__main__':
pytest.main(["-v", "--alluredir=Report/Allure/AllureResult"])
os.system("allure generate -o Report/Allure/AllureResult")
os.system("allure serve Report/Allure/AllureResult")
运行效果:
用法1:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iyjJxacY-1608128106387)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201209120853667.png)]
用法2:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U34vs9D0-1608128106387)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201209120946426.png)]
用法3:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lhnrRkhn-1608128106388)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201209121136406.png)]
@allure.title()
作用
示例:
# test_demo.py
import pytest
import allure
import os
@pytest.fixture(scope='function', autouse=True)
def open_browse(request):
print('function:打开浏览器!')
yield
print('function:关闭浏览器!')
@pytest.fixture(scope='class', autouse=True)
def open_home_page():
print('class:打开主页!')
@allure.step('步骤1')
def run_step_1():
allure.attach("这是一个测试txt附件", 'test_data_1.text', allure.attachment_type.TEXT)
allure.attach.file('./Files/test_data.txt', 'test_data.txt', attachment_type=allure.attachment_type.TEXT)
@allure.step('步骤2')
def run_step_2():
pass
class TestO1:
login_data = [{
'title': '测试有密码的情况', 'user': 'Tom', 'pwd': '111'}, {
'title': '测试无密码的情况', 'user': 'Vicky', 'pwd': ''}]
test_case = ['测试有密码的情况', '测试无密码的情况']
@allure.title('测试用例1')
def test_01(self):
"""测试用例2执行了"""
run_step_1()
@allure.title('测试用例2:{login_data}')
@pytest.mark.parametrize('login_data', login_data)
def test_02(self, login_data):
"""测试用例2执行了"""
print(f'用户名:{login_data["user"]}, 密码{login_data["pwd"]}')
run_step_1()
run_step_2()
if login_data['pwd']:
assert True
else:
assert False
if __name__ == '__main__':
pytest.main(["-v", "--alluredir=Report/Allure/AllureResult", "--clean-alluredir"])
os.system("allure generate -o Report/Allure/AllureResult")
os.system("allure serve Report/Allure/AllureResult")
效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4VWYuN0R-1608128106388)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201211223849324.png)]
主要是为了将allure报告和测试管理系统集成,可以更快速的跳转到公司内部地址
先看源码:
def link(url, link_type=LinkType.LINK, name=None):
return safely(plugin_manager.hook.decorate_as_link(url=url, link_type=link_type, name=name))
def issue(url, name=None):
return link(url, link_type=LinkType.ISSUE, name=name)
def testcase(url, name=None):
return link(url, link_type=LinkType.TEST_CASE, name=name)
示例:
在tittle的示例基础上加上
@allure.title('测试用例2:{login_data}')
@pytest.mark.parametrize('login_data', login_data)
@allure.link('https://www.baid.com')
@allure.link('sss', name='跳转链接1:有name')
@allure.issue('aaa', name='Bug单地址')
@allure.testcase('ddd',name='测试用例地址')
def test_02(self, login_data):
"""测试用例2执行了"""
print(f'用户名:{login_data["user"]}, 密码{login_data["pwd"]}')
run_step_1()
run_step_2()
if login_data['pwd']:
assert True
else:
assert False
效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sVg27htw-1608128106389)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201211230247716.png)]
1.BDD标记装饰器
提供了三个装饰器
示例:
# test_demo.py
import pytest
import allure
import os
@allure.epic("XXX模块")
@allure.feature("特性1")
class TestO1:
@allure.story("故事1")
@allure.title('测试用例1')
def test_01(self):
"""测试用例1执行了"""
pass
@allure.story("故事2")
@allure.title('测试用例2')
def test_02(self):
"""测试用例2执行了"""
pass
@allure.epic("XXX模块")
@allure.feature("特性2")
class TestO2:
@allure.story("故事3")
@allure.title('测试用例3')
def test_03(self):
"""测试用例3执行了"""
pass
@allure.story("故事4")
@allure.title('测试用例4')
def test_04(self):
"""测试用例4执行了"""
pass
if __name__ == '__main__':
pytest.main(["-v", "--alluredir=Report/Allure/AllureResult", "--clean-alluredir"])
os.system("allure generate -o Report/Allure/AllureResult")
os.system("allure serve Report/Allure/AllureResult")
效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LZ4xbqSM-1608128106390)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201212222022693.png)]
从上面的效果来看通过BDD标记装饰器,可以很好的分类用例脚本,当然跟 @pytest.mark.xxx 指定标签运行的方式一样,添加下面的命令行参数,也能分类执行测试用例,效果的话,自行体会。
2.用例优先(问题严重)级别标记
平时写测试用例也会划分优先级,同样allure 也提供用例级别,在 报告可以清晰看到不同级别用例的缺陷数量
示例:
# test_demo.py
import pytest
import allure
import os
@allure.epic("XXX模块")
@allure.feature("特性1")
class TestO1:
@allure.story("故事1")
@allure.title('测试用例1')
@allure.severity('blocker')
def test_01(self):
"""测试用例1执行了"""
assert False
@allure.story("故事2")
@allure.title('测试用例2')
@allure.severity('blocker')
def test_02(self):
"""测试用例2执行了"""
pass
@allure.epic("XXX模块")
@allure.feature("特性2")
class TestO2:
@allure.story("故事3")
@allure.title('测试用例3')
@allure.severity('critical')
def test_03(self):
"""测试用例3执行了"""
assert False
@allure.story("故事4")
@allure.title('测试用例4')
@allure.severity('normal')
def test_04(self):
"""测试用例4执行了"""
pass
if __name__ == '__main__':
pytest.main(["-v", "--alluredir=Report/Allure/AllureResult", "--clean-alluredir"])
os.system("allure generate -o Report/Allure/AllureResult")
os.system("allure serve Report/Allure/AllureResult")
效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3wJG4WKq-1608128106390)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201212223537216.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YXHLtUjV-1608128106391)(/Users/zhangyuxun/Library/Application Support/typora-user-images/image-20201212223603108.png)]
当然也可以和BDD一样,也能通过命令行按优先级执行用例
这个钩子方法可以更清晰的了解用例的执行过程,并获取到每个用例的执行结果。
这里item是测试用例,call是测试步骤,具体执行过程如下:
示例:
# conftest.py
import pytest
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport(item, call):
print('------------------------------------')
# 获取钩子方法的调用结果
out = yield
print('用例执行结果', out)
# 3. 从钩子方法的调用结果中获取测试报告
report = out.get_result()
print('测试报告:%s' % report)
print('步骤:%s' % report.when)
print('nodeid:%s' % report.nodeid)
print('description:%s' % str(item.function.__doc__))
print(('运行结果: %s' % report.outcome))
# test_demo.py
import pytest
import os
class TestO1:
@pytest.fixture(autouse=True)
def login(self):
print('执行前')
yield
print('执行后')
@pytest.mark.usefixtures('login')
def test_01(self, login):
"""测试用例1执行了"""
assert False
if __name__ == '__main__':
pytest.main(["-v"])
运行结果:
collecting ... collected 1 item
test_demo_1.py::TestO1::test_01 ------------------------------------
用例执行结果 .callers._Result object at 0x1032af750>
测试报告:'test_demo_1.py::TestO1::test_01' when='setup' outcome='passed'>
步骤:setup
nodeid:test_demo_1.py::TestO1::test_01
description:测试用例1执行了
运行结果: passed
执行前
------------------------------------
用例执行结果 .callers._Result object at 0x1032afb90>
测试报告:'test_demo_1.py::TestO1::test_01' when='call' outcome='failed'>
步骤:call
nodeid:test_demo_1.py::TestO1::test_01
description:测试用例1执行了
运行结果: failed
FAILED [100%]
test_demo_1.py:14 (TestO1.test_01)
self = .test_demo_1.TestO1 object at 0x1032a2d10>, login = None
@pytest.mark.usefixtures('login')
def test_01(self, login):
"""测试用例1执行了"""
> assert False
E assert False
test_demo_1.py:18: AssertionError
执行后
断言失败
------------------------------------
用例执行结果 .callers._Result object at 0x1032bdbd0>
测试报告:'test_demo_1.py::TestO1::test_01' when='teardown' outcome='passed'>
步骤:teardown
nodeid:test_demo_1.py::TestO1::test_01
description:测试用例1执行了
运行结果: passed