pytest-fixture

资料来源:虫师2020的个人空间-虫师2020个人主页-哔哩哔哩视频

支持类似unittest风格的fixture,即setup和teardown

class类中的方法分类

类方法可以直接调用,需要添加装饰器,修改类中的变量

实例方法,需要先实例化,修改实例方法内的变量

静态方法可以直接调用

class A:
    name = 'jerry'

    #类方法:修改类变量
    @classmethod
    def motify_name(cls):
        return cls.name+'class'

    # 初始化方法
    def __init__(self):
        self.name = 'tom'

    #实例方法 instance Method:修改实例变量
    def hello(self):
        return self.name+'new'

    #静态方法
    @staticmethod
    def static_method(x,y):
        return x+y

if __name__ == '__main__':
    # 类方法可以直接调用
    motify = A.motify_name()
    print("classmethod:",motify)
    #静态方法可以直接调用
    result = A.static_method(3, 4)
    print("static_method:", result)
    #实例方法,需要先实例化
    a = A()
    new_name = a.hello()
    print("类中对象的属性",a.name)
    print("instance Method:",new_name)

class 级别

import pytest

def multiply(a, b):
    """Return the product of two numbers."""
    return a * b


# Test fixture for the multiply function
class TestMultiply:

    @classmethod
    def setup_class(cls):
        print("setup_class(): Executed before any method in this class")

    @classmethod
    def teardown_class(cls):
        print("teardown_class(): Executed after all methods in this class")

    def setup_method(self):
        print("setup_method(): Executed before each test method")

    def teardown_method(self):
        print("teardown_method(): Executed after each test method")

    def test_multiply_3(self):
        """Test multiply with two integers."""
        print("Executing test3")
        assert multiply(3, 4) == 12

    def test_multiply_4(self):
        """Test multiply with an integer and a string (should fail)."""
        print("Executing test4")
        # Note: This test will raise a TypeError due to incompatible types.
        assert multiply(3, 'a') == 'aaa'


module级别

import pytest

# 功能函数
def multiply(a, b):
    return a * b

# ====== fixture ======
def setup_module(module):
    print("setup_module():在整个模块之前执行",module)

def teardown_module(module):
    print("teardown_module():在整个模块之后执行",module)

def setup_function(function):
    print("setup_function():在每个方法之前执行",function)

def teardown_function(function):
    print("teardown_function():在每个方法之后执行",function)

def test_multiply_1():
    print("正在执行test1")
    assert multiply(3, 4) == 12

def test_multiply_2():
    print("正在执行test2")
    assert multiply(3, 'a') == 'aaa'

class TestMultiply:

    def test_multiply_4(self):
        """Test multiply with an integer and a string (should fail)."""
        print("Executing test4")
        # Note: This test will raise a TypeError due to incompatible types.
        assert multiply(3, 'a') == 'aaa'

fixture的简单用例

单个fixture

用fixture之前的原代码

import pytest


# @pytest.fixture()
def init_env():
    print("this is fixture")
    return True


def test_case(init_env):
    if init_env is True:
        print("test case")


if __name__ == '__main__':
    ie = init_env()
    test_case(init_env=ie)

 fixture装饰后

import pytest


@pytest.fixture()
def init_env():
    print("this is fixture")
    return True


def test_case(init_env):
    if init_env is True:
        print("test case")

多个fixture

import pytest


@pytest.fixture()
def first():
    print("this is fixture")
    return "a"


@pytest.fixture()
def second():
    print("this is fixture")
    return 2


@pytest.fixture()
def oder(first,second):
    return first * second


@pytest.fixture()
def expected():
    return "aa"


def test_string(oder,expected):
    print(oder)
    assert oder == expected
    

autouse = True,被装饰的函数自动执行

import pytest


@pytest.fixture()
def first():
    print("this is fixture")
    return "a"


@pytest.fixture()
def second(first):
    print("this is 2fixture")
    return []


@pytest.fixture(autouse=True)
def order(second,first):
    print("自动执行")
    return second.append(first)


def test_string(second,first):
    assert second == [first]

使用范围

scope:=module,session,class,function;

function:每个test都运行,默认是function的scope ,

class:每个 class的所有test只运行一次;

module:每个module的所有test只运行一次;

session:每个session只运行一次

返回参数

在 pytest 中,一个 fixture 可以返回任意数量的数据,包括多个参数。然而,当你想要通过 request 对象在 fixture 内部访问参数化装饰器 (@pytest.mark.parametrize) 提供的参数时,你需要确保正确地设置 fixture 和参数化装饰器之间的关系。在参数化装饰器中直接定义多个参数,并在测试函数中接收它们。然后在 fixture 中,可以直接返回一个元组或者一个字典,其中包含了多个值。

下面是一个示例,展示了如何在一个 fixture 中返回两个参数:

import pytest

# 这个 fixture 返回一个元组,其中包含两个值
@pytest.fixture
def two_values(request):
    # 获取参数化装饰器提供的参数
    first_value = request.param[0]
    second_value = request.param[1]
    
    # 返回一个包含两个值的元组
    return (first_value, second_value)

# 使用 @pytest.mark.parametrize 装饰器提供多组参数
# 直接在测试函数中接收两个参数
@pytest.mark.parametrize("first_value, second_value", [(1, 2), (3, 4)])
def test_with_two_values(first_value, second_value):
    assert first_value < second_value

在这个例子中,two_values fixture 返回一个包含两个值的元组。我们使用 @pytest.mark.parametrize 装饰器来提供两组参数 (1, 2)(3, 4)。然后在测试函数 test_with_two_values 中,我们接收 two_values fixture 的结果,并同时接收 first_valuesecond_value 参数,这些参数是由参数化装饰器提供的。

Teardown - yield 

迭代器

#被测试类所实现功能
import pytest

class Counter:

    def __init__(self):
        self.value = 0

    def increment(self):
        self.value += 1

    def get_value(self):
        return self.value

if __name__ == '__main__':
    c = Counter()
    c.increment()
    c.increment()
    c.increment()
    c.increment()
    print(c.get_value())

import pytest


class Counter:
    def __init__(self):
        self.value = 0

    def increment(self):
        self.value += 1

    def get_value(self):
        return self.value


@pytest.fixture
def counter():
    #setup
    c = Counter()
    print("setup_value:",c.value)

    yield c  #返回实例 c

    #还原数据,用例结束后实现
    c.value=0
    print("teardonw_value:",c.value) 


def test_counter(counter):
    print('test-start')
    assert counter.get_value() == 0
    counter.increment()
    assert counter.get_value() == 1
    counter.increment()
    assert counter.get_value() == 2

终结器 finalizer

同上,value增加后再重置为0

import pytest


class Counter:
    def __init__(self):
        self.value = 0

    def increment(self):
        self.value += 1

    def get_value(self):
        return self.value


@pytest.fixture
def counter(request):#request 参数不需要传值
    #setup
    c = Counter()
    print("setup_value:",c.value)

    def reset_counter():
        #还原数据
        c.value = 0
        print("teardown_value:",c.value)

    request.addfinalizer(reset_counter)

    return c


def test_counter(counter):
    print('test-start')
    assert counter.get_value() == 0
    counter.increment()
    assert counter.get_value() == 1
    counter.increment()
    assert counter.get_value() == 2

fixture装饰的方法的request

在PyTest中,`request`对象是一个特殊的fixture,它提供了对当前测试请求的访问。当你在自定义的fixture函数中使用`request`作为参数时,你实际上可以获得当前测试的详细信息,并且可以利用这些信息来定制fixture的行为。`request`对象提供了很多有用的方法和属性,使你能够更灵活地设置和清理测试环境。

在你提供的fixture示例中,`request`对象被用来添加一个finalizer,即一个在测试结束后自动调用的清理函数。`request.addfinalizer()`方法接受一个可调用对象作为参数,并确保在测试完成时调用它,即使测试失败或抛出了异常。这使得你可以在测试结束后执行一些必要的清理工作,比如关闭文件、重置状态、释放资源等。

以下是一些`request`对象常用的方法和属性:

- `request.param`:如果测试函数使用了参数化装饰器`@pytest.mark.parametrize`,则`request.param`将包含当前测试迭代的参数值。
- `request.function`:返回当前正在运行的测试函数。
- `request.cls`:如果测试函数属于一个测试类,则返回这个测试类。
- `request.module`:返回包含当前测试的模块。
- `request.config`:返回PyTest配置对象,可以从中获取命令行选项和其他配置信息。
- `request.getfixturevalue(name)`:获取其他fixture的返回值。
- `request.cached_setup`:用于缓存setup过程的结果,避免重复执行相同的setup步骤。

总的来说,`request`对象在自定义fixture中提供了一种强大的机制,用于根据测试的具体需求动态调整fixture的行为,从而实现更加复杂的测试场景支持和资源管理。

参数化

10_pytest之fixture装饰器使用(下)_哔哩哔哩_bilibili

直接参数化(Direct Parametrization)

直接参数化是最直观的参数化方法,它在测试函数上使用 @pytest.mark.parametrize 装饰器,直接指定测试函数的参数及其值。这种方法适用于当你的参数可以直接在测试函数中使用,不需要额外的处理或准备。

import pytest

@pytest.mark.parametrize("input, expected", [
    ("3+5", 8),
    ("2+4", 6),
    ("-1+1", 0),
])
def test_eval(input, expected):
    assert eval(input) == expected

间接参数化(Indirect Parametrization)

间接参数化则是通过 fixture 来提供参数,这样可以在参数化测试之前执行一些预处理或设置工作。当你需要在测试前做某些准备工作,比如数据库连接、文件读取、网络请求等,或者当参数的准备较为复杂时,使用间接参数化就很有必要了。

import pytest

@pytest.fixture
def prepared_input(request):
    input_str, expected = request.param
    # 执行一些预处理,比如从数据库获取数据
    return input_str, expected

@pytest.mark.parametrize("prepared_input", [
    ("3+5", 8),
    ("2+4", 6),
    ("-1+1", 0),
], indirect=True)
def test_eval(prepared_input):
    input_str, expected = prepared_input
    assert eval(input_str) == expected

pytest.mark.parametrize的使用

基本使用

@pytest.mark.parametrize("argnames", argvalues)
def test_function(argnames):
    # 测试逻辑

argnames 是一个字符串,包含了测试函数参数的名称,多个参数用逗号分隔。

argvalues 是一个列表,其中的每一个元素都是一组参数值,对应于 argnames 中的参数。

import pytest

@pytest.mark.parametrize("x, y, expected", [
    (1, 2, 3),
    (5, 3, 8),
    (-1, -1, -2),
])
def test_addition(x, y, expected):
    assert x + y == expected

使用 ids

每组参数指定一个标识符(id),这样可以使得测试报告更加可读。ids 应该是一个与 argvalues 同长度的列表,每个元素对应一组参数的描述。

@pytest.mark.parametrize("x, y, expected", [
    (1, 2, 3),
    (5, 3, 8),
    (-1, -1, -2),
], ids=["positive_numbers", "larger_positive_numbers", "negative_numbers"])
def test_addition(x, y, expected):
    assert x + y == expected

复杂参数

如果参数值本身是复杂的结构,如列表或字典,你可以直接在 argvalues 中使用这些结构。

@pytest.mark.parametrize("data", [
    {"input": [1, 2, 3], "expected": 6},
    {"input": [4, 5, 6], "expected": 15},
])
def test_sum_list(data):
    assert sum(data["input"]) == data["expected"]

间接参数化

间接参数化允许你使用 fixture 作为参数源,这样可以在测试函数运行前进行一些初始化工作。要使用间接参数化,你只需在 argnames 后面添加 indirect=True

import pytest

@pytest.fixture
def prepared_data(request):
    if request.param == "case1":
        return {"input": [1, 2, 3], "expected": 6}
    elif request.param == "case2":
        return {"input": [4, 5, 6], "expected": 15}

@pytest.mark.parametrize("prepared_data", ["case1", "case2"], indirect=True)
def test_sum_list(prepared_data):
    assert sum(prepared_data["input"]) == prepared_data["expected"]

fixture两个方法传递参数

直接在 Fixture 方法中调用另一个 Fixture:

如果一个 fixture 需要依赖另一个 fixture 的输出,你可以在一个 fixture 的实现中直接调用另一个 fixture。这是最直接的方式,但要注意保持依赖关系清晰,避免过多的耦合。

import pytest

@pytest.fixture
def fixture_a():
    return "A"

@pytest.fixture
def fixture_b(fixture_a):
    # 使用 fixture_a 的输出
    return fixture_a + "B"

def test_example(fixture_b):
    assert fixture_b == "AB"
使用 request 对象间接获取参数

当你使用 @pytest.mark.parametrize 装饰器时,可以在 fixture 中使用 request 对象来获取参数化装饰器提供的参数。

import pytest

@pytest.fixture
def fixture_a(request):
    # 从 request.param 获取参数
    return request.param

@pytest.fixture
def fixture_b(fixture_a):
    return fixture_a + "B"

@pytest.mark.parametrize("fixture_a", ["A"], indirect=True)
def test_example(fixture_b):
    assert fixture_b == "AB"
使用 Indirect 参数化

如果你需要将一个 fixture 的输出作为另一个 fixture 的参数,你可以使用 indirect 参数化。这允许你将一个 fixture 的返回值直接作为另一个 fixture 的参数。

import pytest

@pytest.fixture
def fixture_a():
    return "A"

@pytest.fixture
def fixture_b(fixture_a):
    return fixture_a + "B"

@pytest.mark.parametrize("fixture_a", ["A"], indirect=True)
@pytest.mark.parametrize("fixture_b", ["AB"], indirect=True)
def test_example(fixture_a, fixture_b):
    assert fixture_a + "B" == fixture_b

通常你不需要为每个 fixture 单独使用 @pytest.mark.parametrize,因为这可能导致不必要的重复测试。更常见的是,一个 fixture 依赖于另一个 fixture 的输出,如第一个例子所示。

使用 Fixture 返回复杂数据结构

如果一个 fixture 需要返回复杂的数据结构,如字典或元组,你可以在测试函数中直接解构这些数据结构。

import pytest

@pytest.fixture
def complex_fixture():
    return {"part_a": "A", "part_b": "B"}

def test_example(complex_fixture):
    part_a, part_b = complex_fixture.values()
    assert part_a + part_b == "AB"
Fixture接收外部参数

让第二个 fixture 既接收第一个 fixture 的返回值,又接收一个额外的参数

import pytest

# 第一个 fixture
@pytest.fixture
def first_fixture():
    return "Hello from first fixture"

# 第二个 fixture,它接收第一个 fixture 的返回值和一个额外的参数
@pytest.fixture
def second_fixture(first_fixture, extra_param):
    return first_fixture + ", " + extra_param

# 使用 indirect=True 指定 extra_param 应该间接地传递给 second_fixture
@pytest.mark.parametrize("extra_param", ["World"], indirect=["second_fixture"])
def test_example_with_params(second_fixture):
    assert second_fixture == "Hello from first fixture, World"

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值