pip install pytest -i https://mirrors.aliyun.com/pypi/simple/
2.1 检索规则
- 运行 test_ 或 * _test.py* 形式的所有目录、文件或函数。
- 运行 Test*开头的类。
2.2 运行方式
python -m pytest [参数] [路径]
pytest [参数] [路径]
import pytest
if __name__ == "__main__":
pytest.main(["", "执行路径"])
夹具,用来 构造测试结构,例如”前置 – 测试 – 后置”的结构,可以用在登录、数据库连接、清理数据等场景。
3.1 作用域
相同测试下仅创建一次fixture,测试执行完后,fixture销毁。
目录:
WorkingDirection/
└────test_fixture/
│ ├────__init__.py
│ └────test_fixture_scope.py
代码:
import pytest
@pytest.fixture(scope="function")
def get_person():
return Person()
def test_1(get_person):
print(id(get_person))
def test_2(get_person):
print(id(get_person))
运行结果:
============================= test session starts =============================
collecting ... collected 2 items
test_fixture_scope.py::test_1 PASSED [ 50%]2713523477768
test_fixture_scope.py::test_2 PASSED [100%]2713523676808
============================== 2 passed in 0.01s ==============================
相同类下仅创建一次fixture,类下最后一条测试执行完后,fixture销毁。
目录:
WorkingDirection/
└────test_fixture/
│ ├────__init__.py
│ └────test_fixture_scope2.py
代码:
import pytest
class Person:
pass
@pytest.fixture(scope="class")
def get_person():
return Person()
class Test_1:
def test_1(self, get_person):
print(id(get_person))
def test_2(self, get_person):
print(id(get_person))
class Test_2:
def test_3(self, get_person):
print(id(get_person))
def test_4(self, get_person):
print(id(get_person))
运行结果:
============================= test session starts =============================
collecting ... collected 4 items
test_fixture_scope2.py::Test_1::test_1 PASSED [ 25%]2139773693128
test_fixture_scope2.py::Test_1::test_2 PASSED [ 50%]2139773693128
test_fixture_scope2.py::Test_2::test_3 PASSED [ 75%]2139773421640
test_fixture_scope2.py::Test_2::test_4 PASSED [100%]2139773421640
============================== 4 passed in 0.01s ==============================
相同模块下仅创建一次fixture,模块下最后一条测试执行完后,fixture销毁。
目录:
WorkingDirection/
└────test_fixture/
│ ├────__init__.py
│ ├────conftest.py
│ ├────test_fixture_scope3.py
│ └────test_fixture_scope4.py
代码:
import pytest
class User:
pass
@pytest.fixture(scope="module")
def get_user():
return User()
def test_1(get_user):
print(id(get_user))
def test_2(get_user):
print(id(get_user))
def test_3(get_user):
print(id(get_user))
def test_4(get_user):
print(id(get_user))
运行结果(test_fixture_scope3.py):
============================= test session starts =============================
collecting ... collected 2 items
test_fixture_scope3.py::test_1 PASSED [ 50%]2713270226440
test_fixture_scope3.py::test_2 PASSED [100%]2713270226440
============================== 2 passed in 0.01s ==============================
运行结果(test_fixture_scope4.py):
============================= test session starts =============================
collecting ... collected 2 items
test_fixture_scope4.py::test_3 PASSED [ 50%]2730364832968
test_fixture_scope4.py::test_4 PASSED [100%]2730364832968
============================== 2 passed in 0.01s ==============================
整个项目下仅创建一次fixture,测试执行完后,fixture销毁。
目录:
WorkingDirection/
└────test_fixture/
│ ├────__init__.py
│ ├────conftest.py
│ └────test_scope_session/
│ │ ├────__init__.py
│ │ ├────test_fixture_scope5.py
│ │ └────test_fixture_scope6.py
代码:
import pytest
class Fruit:
pass
@pytest.fixture(scope="session")
def get_fruit():
return Fruit()
def test_1(get_fruit):
print(id(get_fruit))
def test_2(get_fruit):
print(id(get_fruit))
def test_3(get_fruit):
print(id(get_fruit))
def test_4(get_fruit):
print(id(get_fruit))
运行结果:
============================= test session starts =============================
collecting ... collected 4 items
test_fixture_scope5.py::test_1 PASSED [ 25%]3068522292168
test_fixture_scope5.py::test_2 PASSED [ 50%]3068522292168
test_fixture_scope6.py::test_3 PASSED [ 75%]3068522292168
test_fixture_scope6.py::test_4 PASSED [100%]3068522292168
============================== 4 passed in 0.02s ==============================
Process finished with exit code 0
3.2 可缓存
对于当此运行的测试而言,fixture仅会在作用域内创建一次,重复调用会使用缓存值,而不再执行。
import pytest
num = []
@pytest.fixture
def f1():
print("\n执行了f1...")
num.append("a")
return num
@pytest.fixture
def f2(f1):
print("执行了f2...")
num.append("b")
return num
def test_1(f2, f1):
print(num)
运行结果:
============================= test session starts =============================
collecting ... collected 1 item
test_fixture_cache.py::test_1
执行了f1...
执行了f2...
PASSED [100%]['a', 'b']
============================== 1 passed in 0.02s ==============================
3.3 可参数化
可使用 request来接收fixture的返回值, request.param来接收fixture的参数内容。
import pytest
class User:
def __init__(self, name, age):
self.name = name
self.age = age
data = [
{"name": "zhangsan", "age": 18},
{"name": "lisi", "age": 88}
]
@pytest.fixture(params=data, ids=["用户a", "用户b"])
def get_user(request):
return User(request.param["name"], request.param["age"])
def test_1(get_user):
print(get_user)
运行结果:
============================= test session starts =============================
collecting ... collected 2 items
test_fixture_param.py::test_1[\u7528\u6237a] PASSED [ 50%]<test_fixture.test_fixture_param.User object at 0x0000024104C24508>
test_fixture_param.py::test_1[\u7528\u6237b] PASSED [100%]<test_fixture.test_fixture_param.User object at 0x0000024104CE0188>
============================== 2 passed in 0.01s ==============================
3.4 可嵌套
注意:必须用小作用域的fixture去嵌套大作用域的fixture,否则会报错,如下:
import pytest
num = []
@pytest.fixture
def f1():
print("\n执行了f1...")
num.append("a")
return num
@pytest.fixture(scope="class")
def f2(f1):
print("执行了f2...")
num.append("b")
return num
def test_1(f2, f1):
print(num)
运行结果:
============================= test session starts =============================
collecting ... collected 1 item
test_fixture_cache.py::test_1 ERROR [100%]
test setup failed
ScopeMismatch: You tried to access the 'function' scoped fixture 'f1' with a 'class' scoped request object, involved factories
test_fixture_cache.py:13: def f2(f1)
test_fixture_cache.py:6: def f1()
============================== 1 error in 0.02s ===============================
3.5 yield返回
import pytest
class Test_basket:
basket = None
@pytest.fixture(scope="class")
def create_basket(self):
Test_basket.basket = []
print(f"\n拿来一个篮子: {Test_basket.basket}")
yield Test_basket.basket
Test_basket.basket = None
print(f"\n归还一个篮子: {Test_basket.basket}")
@pytest.fixture
def add_apple(self):
Test_basket.basket.append("苹果")
print("\n添加了苹果")
yield
Test_basket.basket.remove("苹果")
print("拿走了苹果")
@pytest.fixture
def add_banana(self):
Test_basket.basket.append("香蕉")
print("添加了香蕉")
yield
Test_basket.basket.remove("香蕉")
print("拿走了香蕉")
"""
create_basket是类级作用域,add_apple、add_banana是函数级作用域,作用域大的优先执行。
"""
def test_1(self, add_apple, add_banana, create_basket):
print(f"\n检查篮子中的水果: {Test_basket.basket}")
运行结果:
============================= test session starts =============================
collecting ... collected 1 item
test_fixture_yield.py::Test_basket::test_1
拿来一个篮子: []
添加了苹果
添加了香蕉
PASSED [100%]
检查篮子中的水果: ['苹果', '香蕉']
拿走了香蕉
拿走了苹果
归还一个篮子: None
============================== 1 passed in 0.01s ==============================
3.5 可自适应
不用在测试参数中传入fixture名称,fixture会自动调用。
import pytest
class Product:
pass
@pytest.fixture(autouse=True)
def get_product():
print("生产了一个零件")
return Product()
def test_1():
print("执行test_1...")
def test_2():
print("执行test_2...")
运行结果:
============================= test session starts =============================
collecting ... collected 2 items
test_fixture_auto.py::test_1 生产了一个零件
PASSED [ 50%]执行test_1...
test_fixture_auto.py::test_2 生产了一个零件
PASSED [100%]执行test_2...
============================== 2 passed in 0.01s ==============================
- monkeypatch是pytest内置的fixture,可以用来mock数据。
- monkeypatch仅会在代码运行时(内存中)起作用,不会修改源码,因此只对当前的测试生效。
import pytest
class A:
info = {
"name": "小A",
"age": 18
}
@classmethod
def open_website(cls, url):
print(f"\n{cls.info['name']}打开了网站{url}")
class B:
@classmethod
def open_website(cls, url):
print(f"\nB 打开了网站{url}")
@pytest.fixture
def mock_url(monkeypatch):
monkeypatch.setattr(A, "open_website", B.open_website)
monkeypatch.setitem(A.info, "age", 80)
a = A()
def test_1(mock_url):
a.open_website("https://www.baidu.com")
print(a.info)
"""
test_2运行结果说明猴子补丁不会修改源码。
"""
def test_2():
a.open_website("https://www.baidu.com")
print(a.info)
运行结果:
============================= test session starts =============================
collecting ... collected 2 items
test_fixture_mock.py::test_1 PASSED [ 50%]
B 打开了网站https://www.baidu.com
{'name': '小A', 'age': 80}
test_fixture_mock.py::test_2 PASSED [100%]
小A打开了网站https://www.baidu.com
{'name': '小A', 'age': 18}
============================== 2 passed in 0.01s ==============================
5.1 给测试打上标签
通过打标签,可以选择性得运行测试,需要注意,标记需要先注册!
目录:
WorkingDirection/
├────__init__.py
├────main.py
├────pytest.ini
└────test_mark.py
[pytest]
markers=
smoke : "run smoke test"
import pytest
@pytest.mark.smoke
def test_1():
print("运行了test_1...")
@pytest.mark.smoke
def test_2():
print("运行了test_2...")
def test_3():
print("运行了test_3...")
import pytest
if __name__ == "__main__":
pytest.main(["-m smoke", "test_mark.py"])
运行结果(main.py):
============================= test session starts =============================
platform win32 -- Python 3.7.8, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
rootdir: S:\study\studyPytest\test_mark, configfile: pytest.ini
plugins: html-2.1.1, metadata-1.11.0
collected 3 items / 1 deselected / 2 selected
test_mark.py .. [100%]
======================= 2 passed, 1 deselected in 0.02s =======================
5.2 测试参数化
import pytest
@pytest.mark.parametrize("user,password",
[("admin", "132456"), ("wangwu", "456789")],
ids=["管理员登录", "用户登录"])
def test_1(user, password):
print(f"登录者:{user}-{password}")
"""
可以做正交
"""
@pytest.mark.parametrize("sex", ["男", "女"])
@pytest.mark.parametrize("name", ["张三", "李四", "王五"])
def test_2(name, sex):
print(f"查询条件:{name} + {sex}")
运行结果:
============================= test session starts =============================
collecting ... collected 8 items
test_mark2.py::test_1[\u7ba1\u7406\u5458\u767b\u5f55] PASSED [ 12%]登录者:admin-132456
test_mark2.py::test_1[\u7528\u6237\u767b\u5f55] PASSED [ 25%]登录者:wangwu-456789
test_mark2.py::test_2[\u5f20\u4e09-\u7537] PASSED [ 37%]查询条件:张三 + 男
test_mark2.py::test_2[\u5f20\u4e09-\u5973] PASSED [ 50%]查询条件:张三 + 女
test_mark2.py::test_2[\u674e\u56db-\u7537] PASSED [ 62%]查询条件:李四 + 男
test_mark2.py::test_2[\u674e\u56db-\u5973] PASSED [ 75%]查询条件:李四 + 女
test_mark2.py::test_2[\u738b\u4e94-\u7537] PASSED [ 87%]查询条件:王五 + 男
test_mark2.py::test_2[\u738b\u4e94-\u5973] PASSED [100%]查询条件:王五 + 女
============================== 8 passed in 0.05s ==============================
5.3 跳过测试
可以直接跳过测试,或者满足某种条件时,跳过测试(测试不会被执行)。
from random import random
import pytest
i = int(random() * 10)
@pytest.mark.skip("测试作废")
def test_1():
print("test_1 执行了...")
@pytest.mark.skipif(f"{i} > 5")
def test_2():
print("test_1 执行了...")
运行结果:
============================= test session starts =============================
collecting ... collected 2 items
test_mark_skip.py::test_1 SKIPPED (测试作废) [ 50%]
Skipped: 测试作废
test_mark_skip.py::test_2 SKIPPED (condition: 8 > 5) [100%]
Skipped: condition: 8 > 5
============================= 2 skipped in 0.01s ==============================
作用是 期望测试用例是失败的(比如已知前置条件会出问题,设计该测试时可以让这个测试预期失败),若测试执行失败,则结果为xfailed,若测试执行成功,则结果为xpassed,除此之外,xfail与skip不同的是,xfail的测试是会被执行的,而skip是直接跳过测试。
from random import random
import pytest
@pytest.mark.xfail
def test_1():
print("执行了test_1...")
assert 1
@pytest.mark.xfail
def test_2():
print("执行了test_2...")
assert 0
"""
test_3中,pytest.xfail 后面的代码不会被执行。
"""
def test_3():
i = random()*10
print("开始执行test_3...")
if i > 5:
pytest.xfail("功能未完成,停止执行")
print("执行了test_3...")
运行结果:
============================= test session starts =============================
collecting ... collected 3 items
test_mark_xfail.py::test_1 XPASS [ 33%]执行了test_1...
test_mark_xfail.py::test_2 XFAIL [ 66%]执行了test_2...
@pytest.mark.xfail
def test_2():
print("执行了test_2...")
> assert 0
E assert 0
test_mark_xfail.py:15: AssertionError
test_mark_xfail.py::test_3 XFAIL (功能未完成,停止执行) [100%]开始执行test_3...
def test_3():
i = random()*10
print("开始执行test_3...")
if i > 5:
> pytest.xfail("功能未完成,停止执行")
E _pytest.outcomes.XFailed: 功能未完成,停止执行
test_mark_xfail.py:27: XFailed
======================== 2 xfailed, 1 xpassed in 0.02s ========================
钩子函数,即 Hook函数,本质上是 回调函数(即函数当作参数传入,例如,函数A的参数接收函数B,那么函数B在函数A内部被调用了,则函数B就是回调函数,好处是,实现了函数之间的解耦,函数A不需要关注函数B是如何实现的),Pytest提供了许多Hook函数,在测试生命周期(初始化 -> 测试收集 -> 测试执行 -> 输出报告)的特定阶段会 自动调用,在 conftest.py文件中 实现Hook函数,从而对测试阶段进行干预。
6.1 解决ids控制台显示乱码
目录:
WorkingDirection/
├────__init__.py
├────conftest.py
└────test_hook.py
def pytest_collection_modifyitems(items):
for item in items:
item.name = item.name.encode("utf-8").decode("unicode_escape")
item._nodeid = item.nodeid.encode("utf-8").decode("unicode_escape")
import pytest
@pytest.mark.parametrize("data", ["test1", "test2"], ids=["测试1", "测试2"])
def test_1(data):
print(data)
运行结果:
============================= test session starts =============================
collecting ... collected 2 items
test_hook.py::test_1[测试1] PASSED [ 50%]test1
test_hook.py::test_1[测试2] PASSED [100%]test2
============================== 2 passed in 0.01s ==============================
6.2 自定义命令行参数实现环境切换
def pytest_addoption(parser, pluginmanager):
group = parser.getgroup("JN...自定义参数")
group.addoption(
"--env",
dest="env",
default="test",
help="用作切换环境"
)
@pytest.fixture(scope="session", autouse=True)
def env(request):
return request.config.getoption("--env")
def test_2(env):
if env == "dev":
pass
else:
assert 0
=========== 1 passed, 40 deselected, 2 warnings in 0.06s ========
7.1 常用的断言方式
- 判断是否相等,assert Actual == Expected
- 判断是否不相等,assert Actual != Expected,其他如 >、
7.2 断言预期异常
import pytest
def division(a, b):
return a / b
def test_1():
with pytest.raises(ZeroDivisionError):
division(1, 2)
def test_2():
with pytest.raises(ZeroDivisionError):
division(1, 0)
运行结果:
============================= test session starts =============================
collecting ... collected 2 items
test_assert_exception.py::test_1 FAILED [ 50%]
test_assert_exception.py:7 (test_1)
def test_1():
with pytest.raises(ZeroDivisionError):
> division(1, 2)
E Failed: DID NOT RAISE <class 'ZeroDivisionError'>
test_assert_exception.py:10: Failed
test_assert_exception.py::test_2 PASSED [100%]
========================= 1 failed, 1 passed in 0.02s =========================
Original: https://blog.csdn.net/Augenstern_9512/article/details/126311015
Author: JN…
Title: 测试笔记:学习Pytest框架
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/772151/
转载文章受原作者版权保护。转载请注明原作者出处!