在py.test中如何在每个测试前后运行代码?

182 投票
8 回答
203807 浏览
提问于 2025-04-17 23:47

我想在我的测试套件中的每个测试之前和之后运行一些额外的准备和清理检查。我看过一些固定的设置,但不太确定这是否是正确的方法。我需要在每个测试之前运行准备代码,并且在每个测试之后运行清理检查。

我的需求是检查一些代码是否没有正确清理:它留下了临时文件。在我的准备阶段,我会检查这些文件,而在清理阶段我也会检查这些文件。如果发现多余的文件,我希望测试能失败。

8 个回答

6

你可以使用装饰器,但可以通过编程的方式来实现,这样就不需要在每个方法里都加上装饰器了。

在下面的代码中,我假设了几个事情:

测试方法的命名都是类似于:"testXXX()"

装饰器是添加在实现测试方法的同一个模块里的。

def test1():
    print ("Testing hello world")

def test2():
    print ("Testing hello world 2")

#This is the decorator
class TestChecker(object):
    def __init__(self, testfn, *args, **kwargs):
        self.testfn = testfn

    def pretest(self):
        print ('precheck %s' % str(self.testfn))
    def posttest(self):
        print ('postcheck %s' % str(self.testfn))
    def __call__(self):
        self.pretest()
        self.testfn()
        self.posttest()


for fn in dir() :
    if fn.startswith('test'):
        locals()[fn] = TestChecker(locals()[fn])

现在如果你调用这些测试方法……

test1()
test2()

输出应该类似于:

precheck <function test1 at 0x10078cc20>
Testing hello world
postcheck <function test1 at 0x10078cc20>
precheck <function test2 at 0x10078ccb0>
Testing hello world 2
postcheck <function test2 at 0x10078ccb0>

如果你的测试方法是类的方法,这种方法同样适用。例如:

class TestClass(object):
    @classmethod
    def my_test(cls):
        print ("Testing from class method")

for fn in dir(TestClass) :
    if not fn.startswith('__'):
        setattr(TestClass, fn, TestChecker(getattr(TestClass, fn)))

调用 TestClass.my_test() 会打印:

precheck <bound method type.my_test of <class '__main__.TestClass'>>
Testing from class method 
postcheck <bound method type.my_test of <class '__main__.TestClass'>>
14

你可以使用Pytest的模块级别的设置和拆卸功能。

这里有个链接

http://pytest.org/latest/xunit_setup.html

它的工作原理如下:

 def setup_module(module):
     """ setup any state specific to the execution of the given module."""

 def teardown_module(module):
     """ teardown any state that was previously setup with a setup_module
     method."""

 Test_Class():
        def test_01():
          #test 1 Code

在这个测试之前,它会调用setup_module,而在测试完成后会调用teardown_module

你可以在每个测试脚本中包含这个功能,这样每个测试都会运行它。

如果你想使用一些在某个目录下所有测试都通用的东西,可以使用包/目录级别的功能,像nose框架那样。

http://pythontesting.net/framework/nose/nose-fixture-reference/#package

在包的__init__.py文件中,你可以加入以下内容:

     def setup_package():
       '''Set up your environment for test package'''

     def teardown_package():
        '''revert the state '''
18

Fixtures就是你需要的东西。它们就是为了这个目的设计的。

你可以选择使用pytest风格的fixtures,或者使用setupteardown(可以在模块、类或方法级别使用)这种xUnit风格的fixtures,这主要取决于具体情况和个人喜好。

根据你描述的情况,似乎你可以使用pytest的自动使用fixtures
或者使用xUnit风格的函数级别的setup_function()/teardown_function()

Pytest为你提供了全面的支持。信息量大到可能让人感觉像是打开了消防水龙头一样。

79

你可以使用一个 fixture 来实现你想要的功能。

import pytest

@pytest.fixture(autouse=True)
def run_before_and_after_tests(tmpdir):
    """Fixture to execute asserts before and after a test is run"""
    # Setup: fill with any logic you want

    yield # this is where the testing happens

    # Teardown : fill with any logic you want

详细解释

  1. @pytest.fixture(autouse=True)来自文档"有时候,你可能希望在不明确声明函数参数或使用 usefixtures 装饰器的情况下,自动调用 fixtures." 因此,这个 fixture 每次执行测试时都会运行。

  2. # 设置:填入你想要的逻辑,这些逻辑会在每个测试实际运行之前执行。在你的情况下,你可以添加一些断言语句,这些语句会在实际测试之前执行。

  3. yield,正如注释所示,这里是进行测试的地方。

  4. # 清理:填入你想要的逻辑,这些逻辑会在每个测试之后执行。无论测试过程中发生什么,这些逻辑都一定会运行。

注意:pytest 中,测试失败和执行测试时出错是有区别的。失败表示测试在某种程度上没有通过,而错误则表示你无法进行正常的测试。

考虑以下示例:

测试运行前断言失败 -> 错误

import pytest


@pytest.fixture(autouse=True)
def run_around_tests():
    assert False # This will generate an error when running tests
    yield
    assert True

def test():
    assert True

测试运行后断言失败 -> 错误

import pytest


@pytest.fixture(autouse=True)
def run_around_tests():
    assert True
    yield
    assert False

def test():
    assert True

测试失败 -> 失败

import pytest


@pytest.fixture(autouse=True)
def run_around_tests():
    assert True
    yield
    assert True

def test():
    assert False

测试通过 -> 通过

import pytest


@pytest.fixture(autouse=True)
def run_around_tests():
    assert True
    yield
    assert True

def test():
    assert True
228

py.test 的 fixtures 是一种技术上可行的方法,可以帮助你实现目标。

你只需要像这样定义一个 fixture:

@pytest.fixture(autouse=True)
def run_around_tests():
    # Code that will run before your test, for example:
    files_before = # ... do something to check the existing files
    # A test function will be run at this point
    yield
    # Code that will run after your test, for example:
    files_after = # ... do something to check the existing files
    assert files_before == files_after

通过设置 autouse=True,这个 fixture 会在同一个模块中每个测试函数运行时自动调用。

不过,有一点需要注意。 在设置和拆卸时进行断言是一个有争议的做法。我感觉 py.test 的主要作者们并不喜欢这样做(我自己也不喜欢,所以这可能影响了我的看法),所以在使用过程中你可能会遇到一些问题或不太顺利的地方。

撰写回答