识别的测试文件
test_*.py
*_test.py
识别的用例
Test*类包含的所有test_*的方法(测试类不能带有__init__方法)
不在class中的所有的test_*方法
pytest也可以执行unittest框架写的用例和方法
控制台执行
直接执行 pytest 文件名
pytest -v 文件名 输出详细信息
pytest -s 文件名 把测试用例中print的内容也打印出来
pytest 文件名.py::类名 运行某个模块中的某个类
pytest 文件名.py::类名::方法名 运行某个模块中的某个类里面的方法
pytest -v -k “类名 and not 方法名” 跳过执行某个用例
pytest -m [标记名] @pytest.mark.[标记名] 将运行有这个标记的测试用例
pytest -x 文件名 一旦运行到报错就停止运行
pytest --maxfail=[num] 当运行错误达到num时就停止运行
测试失败后要重新运行n次,要在重新运行之间添加延迟时间,间隔n秒再运行
安装 pip install pytest-rerunfailures
执行 pytest --reruns 3 -v -s 文件名.py 重新运行3次
执行 pytest -v --reruns 5 --reruns-delay 1 重新运行5次,每次运行间隔1秒
同一方法内写多条断言,第一条报错后,后面也继续执行
安装 pip install pytest-assume
pytest.assume(1==4)
pycharm中运行
主函数中 pytest.main("-v -x 模块名") 在引号中添加所需要的参数
主函数中 pytest.main([’-v’,’-x’])
pytest框架结构
模块级(setup_module/teardown_module)模块始末,全局的(优先级最高)
函数级(setup_function/teardown_function)只对函数用例生效(不在类中)
类级(setup_class/teardown_class)只在类中前后执行一次(在类中)
方法级(setup_method/teardown_method)开始于方法始末(在类中)
类里面的(setup/teardown)运行在调用方法的前后
pytest-fixture的用法
场景
用例1需要登录
用例2不需要登录
用例3需要登录
步骤
引入pytest
在登录的函数上面加上@pytest.fixture()
在要使用的测试方法中传入(登录函数名称),就先登录
不传入的就不登录直接执行测试方法
import pytest
@pytest.fixture()
def login():
print('login')
def test_one(login):
print('one')
assert 1 == 1
def test_two():
print('two')
assert 2 == 2
def test_three(login):
print('three')
assert 3 == 3
# 入口函数
if __name__ == '__main__':
## ??好像没有生效,后面再看看
## 解释器(python解释器才支持)的问题
## 要写成list,字符串格式高版本不支持
pytest.main("-v --reruns 3")
conftest.py
用来放置公共的方法,测试的文件中可以直接使用里面的方法,不需要引入
场景:与其他人员合作开发时,公共模块要在不同文件中,要在大家都能访问的地方
解决:conftest.py这个文件进行数据共享,并且他可以放在不同位置起着不同的范围共享作用
执行:系统执行到参数login时先从本文件中查找是否有这个文字的变量,之后在conftest中找是否有
步骤:将登录模块带@pytest.fixture写在conftest.py中
注意点
conftest文件名不能更改
conftest.py与运行的用例要在同一个package下,并且有__init__.py文件
不需要import导入conftest.py,pytest会自动查找
全局的配置和前期工作都可以写在这里,放在某个包下,就是这个包数据共享的地方
yield
import pytest
# yield 在第一次执行到yield时,只执行yield之前的内容
# 第二次执行到yield时,只执行yield之后到内容
@pytest.fixture(scope="module")
def login():
print('login')
yield
print('clear')
def test_one(login):
print('one')
assert 1 == 1
def test_two():
print('two')
assert 2 == 2
def test_three(login):
print('three')
assert 3 == 3
if __name__ == '__main__':
## ??好像没有生效,后面再看看
pytest.main("-v --reruns 3")
结果
test_python_file.py login
.one
.two
.three
clear
@pytest.fixture(autouse=True) 将该方法自动应用到每条测试用例中
import pytest
@pytest.fixture(autouse=True)
def login():
print('login')
def test_one():
print('one')
assert 1 == 1
def test_two():
print('two')
assert 2 == 2
def test_three():
print('three')
assert 3 == 3
if __name__ == '__main__':
## ??好像没有生效,后面再看看
pytest.main("-v --reruns 3")
结果
test_python_file.py login
.one
login
.two
login
.three
参数化
@pytest.mark.parametrize()
@pytest.mark.parametrize("content,expected", [("3+5", 8), ("2+5", 7), ("7*5", 35)])
def test_eval(content, expected):
assert eval(content) == expected
@pytest.mark.parametrize("x", [1, 2])
@pytest.mark.parametrize("y", [3, 4])
def test_foo(x, y):
print(f"x的值为{x}")
print(f"y的值为{y}")
# 共输出4组数据,(1,3)(2,3)(2,3)(2,4)
test_user_data = ['xiaoming', 'xiaohong']
@pytest.fixture(scope="module")
def login_r(request):
# 接收传入的参数
user = request.param
print(f"login_r登录的用户为{user}")
return user
# indirect=True 可以把传过来的参数当函数执行
@pytest.mark.parametrize("login_r", test_user_data, indirect=True)
def test_login(login_r):
a = login_r
print(f"login登录的用户为{a}")
对应结果
test_python_file.py ....x的值为1
y的值为3
.x的值为2
y的值为3
.x的值为1
y的值为4
.x的值为2
y的值为4
login_r登录的用户为xiaoming
.login登录的用户为xiaoming
login_r登录的用户为xiaohong
.login登录的用户为xiaohong
跳过测试用例
@pytest.mark.skip()
@pytest.mark.skipif()
# 跳过测试用例
@pytest.mark.skip("此次测试不执行该条用例")
def test_skip():
print("这条测试用例被跳过了")
# 在满足条件时,跳过测试用例
@pytest.mark.skipif(sys.platform == 'darwin', reason="不在macOs上执行")
def test_skip_if():
print('该条测试用例有条件的被跳过')
对应结果:
test_python_file.py s
Skipped: 此次测试不执行该条用例
s
Skipped: 不在macOs上执行
功能测试尚未实施或尚未修复的错误,当用例执行成功时,执行结果为xpass;当用例执行失败时,执行结果为xfail
@pytest.mark.xfail
@pytest.mark.xfail
def test_fail():
print('test_xfail')
raise Exception
@pytest.mark.xfail
def test_pass():
print('test_xfail')
对应结果
test_python_file.py xtest_xfail
@pytest.mark.xfail
def test_fail():
print('test_xfail')
> raise Exception
E Exception
test_python_file.py:52: Exception
Xtest_xfail
[100%]
======================== 1 xfailed, 1 xpassed in 0.09s =========================
Process finished with exit code 0
自定义标记mark只执行某部分用例
@pytest.mark.XXX 其中XXX为自定义标签
import pytest
import sys
@pytest.mark.login
def test_login_1():
print("test_login_1")
@pytest.mark.login
def test_login_2():
print("test_login_2")
@pytest.mark.login
def test_login_3():
print("test_login_3")
@pytest.mark.search
def test_search_1():
print("test_search_1")
@pytest.mark.search
def test_search_2():
print("test_search_2")
@pytest.mark.search
def test_search_3():
print("test_search_3")
conftest.py中的文件
def pytest_configure(config):
markers_list = ["search", "login"]
for markers in markers_list:
config.addinivalue_line(
"markers", markers
)
控制台执行
pytest test_python_file.py -m login
执行结果
=================================================== test session starts ====================================================
platform darwin -- Python 3.7.3, pytest-5.4.3, py-1.8.1, pluggy-0.13.1
rootdir: /Users/yuanmeng/Desktop/lesson_script/fund/gdp
plugins: rerunfailures-9.0, metadata-1.9.0, allure-pytest-2.8.13, assume-2.2.1, html-2.1.1, forked-1.1.3, xdist-1.32.0
collected 6 items / 3 deselected / 3 selected
test_python_file.py ... [100%]
============================================= 3 passed, 3 deselected in 0.05s ==============================================
多线程并行执行用例
pytest分布式执行插件:pytest-xdist
多个cpu或主机执行前提:用例之间是独立的,没有先后顺序,随机都能执行,可重复运行不影响其他用例
安装:pip3 install pytest-xdist
多个cpu并行执行用例,直接加 -n 3,其中3是并行数量
pytest-html生成报告
安装:pip install pytest-html
生成html报告:pytest -v -s --html=report.html --self-contained-html
pytest 高级用法
pytest --collect-only 只收集测试用例不运行
-k 满足表达式的都会执行
-m 满足标签的才会执行(测试用例添加 pytest.mark.标签名)
pytest --junit-xml=path 生成xml格式的测试结果
测试用例执行顺序
unittest 测试用例名称的编码为顺序
pytest以编写测试用例的先后顺序
pytest-ordering 修改测试用例的执行顺序
import pytest
@pytest.mark.run(order=2)
def test_foo():
assert True
@pytest.mark.run(order=1)
def test_bar():
assert True
执行结果
$ py.test test_foo.py -vv
============================= test session starts ==============================
platform darwin -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- env/bin/python
plugins: ordering
collected 2 items
test_foo.py:7: test_bar PASSED
test_foo.py:3: test_foo PASSED
=========================== 2 passed in 0.01 seconds ===========================
pytest测试用例执行顺序
import pytest
# fixtures documentation order example
order = []
@pytest.fixture(scope="session")
def s1():
order.append("s1")
@pytest.fixture(scope="module")
def m1():
order.append("m1")
@pytest.fixture
def f1(f3):
order.append("f1")
@pytest.fixture
def f3():
order.append("f3")
@pytest.fixture(autouse=True)
def a1():
order.append("a1")
@pytest.fixture
def f2():
order.append("f2")
def test_order(f1, m1, f2, s1):
assert order == ["s1", "m1", "a1", "f3", "f1", "f2"]
执行结果
s1: is the highest-scoped fixture (session).
m1: is the second highest-scoped fixture (module).
a1: is a function-scoped autouse fixture: it will be instantiated before other fixtures within the same scope.
f3: is a function-scoped fixture, required by f1: it needs to be instantiated at this point
f1: is the first function-scoped fixture in test_order parameter list.
f2: is the last function-scoped fixture in test_order parameter list.
执行命令时加上参数 --setup-show 可以查看执行顺序
pytest.ini 改变pytest的运行行为
[pytest]
addopts = -v -s
python_files = 'abc*'
python_classes = 'Abc*'
python_functions = 'abc*'
pytest工厂模式
@pytest.fixture
def make_customer_record():
def _make_customer_record(name):
return {"name": name, "orders": []}
return _make_customer_record
def test_customer_records(make_customer_record):
customer_1 = make_customer_record("Lisa")
customer_2 = make_customer_record("Mike")
customer_3 = make_customer_record("Meredith")