将参数传递给 pytest fixture 以进行拆卸

pass a parameter to a pytest fixture for teardown

问题: 清理从测试中创建的测试工件。在下面的例子中,如何使用 pytest fixture 从数据库中删除在测试期间创建的单个行? (并不是每 运行 之后都应该从 table 中删除所有内容。否则可以使用删除所有行或删除 table。创建行的行标识符在测试时保存在一个函数变量中。

是否可以将测试期间创建的变量作为参数传递到 pytest 中的夹具中? fixture 需要始终 运行 测试是通过失败还是成功完成的。在测试 运行 之前,行标识符是未知的。

用夹具说明的问题

@pytest.fixture()
def clean_up_db_row(row_id):
    yield
    delete_from_db(self.row_id). # code to delete the row based on the id


def test_something_added_to_database(clean_up_db_row):
    row_id = create_db_row()  # function under test
    ...
    assert row_id in db  # test that information added to the database

    # the clean_up_db_row fixture will always run but how will it know about the id variable defined in the function?

如果断言在测试中途失败,则在清理到最后时不会删除测试期间添加的行。因为测试停止执行。

问题示例是没有 pytest 夹具:

def clean_up_db_row(row_id):
    yield
    delete_from_db(row_id). # code to delete the row based on the id


def test_something_added_to_database():
    row_id = create_db_row()  # function under test
    ...
    assert row_id in db  # test that information added to the database
    clean_up_db_row(row_id)  # this won’t run if there is a failure

没有 pytest fixture 的解决方案


def clean_up_db_row(row_id):
    yield
    delete_from_db(row_id). # code to delete the row based on the id

def test_something_added_to_database():
    row_id = create_db_row()  # function under test
    ...
    try:
        assert row_id in db  # test that information added to the database
    except Exception as e:
        raise e
    finally:
        clean_up_db_row(row_id)  # this will always run but doesn’t use a fixture

在 class

上使用实例变量的潜在解决方案
class TestCaseCleanUp:

    @pytest.fixture(autouse=True)
    def clean_up_db_row(self):
        yield
        delete_from_db(self.row_id). # code to delete the row based on the id


    def test_something_added_to_database(self):
        self.row_id = create_db_row()  # function under test
        ...
        assert self.row_id in db  # test that information added to the database
        # the autouse fixture can use the self.row_id assigned

fixture 产生的对象被运行器注入到测试上下文中。这可以是你想要的任何对象,测试可以自由改变它。

这是一个适合您的模式:

@pytest.fixture()
def clean_up_db_rows():
    row_ids = []
    yield row_ids
    for row_id in row_ids:
        delete_from_db(row_id)


def test_something_added_to_database(clean_up_db_rows):
    row_id = create_db_row()  # function under test
    clean_up_db_rows.append(row_id)
    ...
    assert row_id in db  # test that information added to the database

我找到了另一个解决方案here:

Create a class in conftest.py and import it in your test module. To protect yourself from errors caused by failed testcases that were suppose to add values in the class, assign None to all the known values.

conftest.py:

class ValueStorage:
    value1 = None
    value2 = None

test_mytest.py:

from conftest import ValueStorage

def test_one():
    ValueStorage.value1 = "anything you want here"
def test_two():
    assert ValueStorage.value1 == "anything you want here"
    ValueStorage.value2 = ValueStorage.value1

class TestClass:
    def test_three(self):
        assert ValueStorage.value1 == ValueStorage.value2

You can pass dictionaries and lists and anything you feel like. If you require persistence (want the values to be kept from one run to another you can write them in a json file or use the cache option)