在 Python Pytest 中传递函数到测试文件

📅 发布时间:2026/7/16 17:40:38 👁️ 浏览次数:
在 Python Pytest 中传递函数到测试文件
原文towardsdatascience.com/passing-functions-to-test-files-in-python-pytest-0f5fe9efed5dPYTHON 编程https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/4f1a41dfb22cac418f5f2d8e4d634377.png夹具有助于在测试函数中重用对象包括函数。图片由rivage在Unsplash提供当您使用 Pytest 进行 Python 单元测试时您可以使用在conftest.py文件中定义的 Pytest 夹具将对象传递到测试文件中。我使用了复数形式因为您可以定义任意数量的conftest.py文件它们的位置定义了它们的范围。当我刚开始学习 Python 单元测试时我经常想知道如何定义一个辅助函数用于测试并在选定的测试函数中使用它。如果您只需要在一个测试文件中使用这个函数没问题只需在这个文件中定义该函数然后就可以使用了。如果您需要在多个测试文件中使用完全相同的函数怎么办一个解决方案是在所有这些文件中重新定义该函数——但这显然违反了DRY不要重复自己原则。那么该如何做呢我注意到很多人都在问这个问题——因此有了这篇文章。您会看到如果您只了解pytest夹具的概念解决方案非常简单甚至相当自然。夹具本文的目的不是介绍 Pytest 夹具。由于我计划写一篇专门的文章来介绍这个主题以下将只提供一个非常简短的介绍。如果您想了解更多关于它们的信息可以从pytest文档中学习关于夹具 - pytest 文档简而言之您可以使用夹具传递各种类型的对象主要是数据和配置到测试文件中。您需要在conftest.py文件中定义一个夹具它将在该conftest.py文件的作用域内可用即在该文件所在的同一文件夹及其所有子文件夹中的所有测试文件中。让我们分析一个简单的例子。为了简化您可以将以下三个文件放在同一个文件夹中# foo.pydeffoo(x,y):returnxy# conftest.pyimportpytestpytest.fixturedefx_y_int():return20,50pytest.fixturedefx_y_str():return20,50# test_foo.pyimportfoodeftest_foo_ints(x_y_int):x,yx_y_intassertfoo.foo(x,y)70deftest_foo_ints(x_y_str):x,yx_y_strassertfoo.foo(x,y)2050第一个模块foo定义了一个函数foo()它接受两个参数。在conftest.py文件中我们定义了两个夹具x_y_int和x_y_str。前者保持整数值而后者为x和y提供字符串值。如您所见一个夹具fixture是一个函数。为了在测试函数中使用它您需要像在上述test_foo.py中那样将其作为这个测试函数的参数提供。这两个测试将会通过test_foo_ints(x_y_int): 我们使用x_y_int设置它保持一个包含(20, 50)的元组。在解包后我们得到x 20和y50。test_foo_str(x_y_str): 我们使用x_y_str设置它保持一个包含(20, 50)的元组。在解包后我们得到x 20和y50。这就是使用pytest设置的基本概念。你可以使用它们将任何对象传递给在定义它们的conftest.py文件作用域内的所有测试文件。将函数传递给测试文件我猜很多人尝试以常规 Python 方式从conftest.py文件传递辅助函数即通过导入。让我们考虑以下一个非常简单的例子。想象我们正在为以下模块实现单元测试# foomodule.pydeffootuple(x):returnx,x**2,x**3我们希望使用以下辅助函数进行测试defis_tuple_fine(t:tuple)-None:msgs{type:Its not a tuple,length:Length of the tuple isnt 3,values:t[1] must be t[0]**2 and t[2] must be t[0]**3}assertisinstance(t,tuple)isTrue,msgs[type]assertlen(t)3,msgs[length]assertt[1]t[0]**2andt[2]t[0]**3,msgs[values]该函数执行三个检查断言一个对象t是一个元组它是一个包含三个元素的元组以下条件成立t[1] t[0]**2和t[2] t[0]**3下面我将讨论三种解决方案。从 conftest 导入函数我首先讨论这个解决方案因为对许多人来说这似乎是做这件事最自然的方式通常当我们需要从另一个模块中获取函数时我们会导入它不是吗虽然这是真的但这个解决方案只会在一个特定的场景下工作所以我不会认为这是一个好的方法因为它不是通用的所以它看起来不自然。说实话我还没有在实际情况中看到过它的使用。因此我将讨论它但只是为了说明它在什么时候有效。肯定的是不要使用这种方法。让我们创建一个包含此函数和一些其他设置的conftest.py文件# conftest.pyimportpytestdefis_tuple_fine(t:tuple)-None:msgs{type:Its not a tuple,length:Length of the tuple isnt 3,values:t[1] must be t[0]**2 and t[2] must be t[0]**3}assertisinstance(t,tuple)isTrue,msgs[type]assertlen(t)3,msgs[length]assertt[1]t[0]**2andt[2]t[0]**3,msgs[values]pytest.fixturedefints_1():return1pytest.fixturedefints_2():return2现在测试文件# test_foomodule.pyimportfoomodulefromconftestimportis_tuple_finedeftest_foo(ints_1):intsfoomodule.footuple(ints_1)is_tuple_fine(ints)assertints(1,1,1)deftest_foo_ints_2(ints_2):intsfoomodule.footuple(ints_2)is_tuple_fine(ints)assertints(2,4,8)这种解决方案只会在一种情况下有效当conftest.py文件位于与测试文件相同的文件夹中时。测试将通过。然而当conftest.py文件保存在其他位置目录树的上层时——这是一个相当常见的场景——你会看到以下错误https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/466862b53c002ac6e2935e5db36feaa0.png从 conftest.py 文件导入函数后的 Pytest 失败。作者截图这就是为什么你不应该考虑这种方法。即使现在它可行以后你可能需要更改测试的结构然后测试将开始失败如上所示。在测试文件中定义辅助函数实际上这是一个最简单的情况在某种情况下是一个好方法当你需要函数只在单个测试文件中可用时。那么只需在这个特定的测试文件中定义该函数即可——不需要在conftest.py中这样做。这意味着将is_tuple_fine()的定义移动到test_foo模块。然而如果你需要辅助函数在多个测试文件中可用你将不得不在其他文件中重新定义它——这种方法绝对应该避免因为它违反了DRY不要重复自己原则。这时下一个解决方案就派上用场了。将函数作为固定装置传递这是一个通用的解决方案因为固定装置正是为此而创建的将对象传递给测试文件。如何做到这一点让我重复一下我上面用过的句子你可以使用它们[固定装置]将任何对象传递给定义它们的conftest.py文件范围内的所有测试文件。任何对象为什么不是一个函数呢在 Python 中函数就像任何其他对象一样是一个对象这是我通常这样做的方式。我在conftest.py文件中定义了我需要的函数作为一个私有函数因此以下划线为前缀即_is_tuple_fine()。然后我定义了一个公共固定装置其名称与函数的实际名称相同即is_tuple_fine()。所以# conftest.pyimportpytestdef_is_tuple_fine(t:tuple)-None:msgs{type:Its not a tuple,length:Length of the tuple isnt 3,values:t[1] must be t[0]**2 and t[2] must be t[0]**3}assertisinstance(t,tuple)isTrue,msgs[type]assertlen(t)3,msgs[length]assertt[1]t[0]**2andt[2]t[0]**3,msgs[values]pytest.fixture(scopesession)defis_tuple_fine():return_is_tuple_finepytest.fixturedefints_1():return1pytest.fixturedefints_2():return2当实际函数非常短时你可以在固定装置内部定义它。选择更清晰、更易读的方法。注意session范围。Pytest 固定装置的默认范围是function这意味着固定装置在每个测试函数结束时被销毁。这个特定的固定装置可以在整个会话中工作因此没有必要在中间销毁它。你可以在Pytest 文档中了解更多关于固定装置范围的信息。现在为了在测试文件中使用我们的函数你需要像使用任何其他固定装置一样使用它importfoomoduledeftest_foo_ints_1(ints_1,is_tuple_fine):intsfoomodule.footuple(ints_1)is_tuple_fine(ints)assertints(1,1,1)deftest_foo_ints_2(ints_2,is_tuple_fine):intsfoomodule.footuple(ints_2)is_tuple_fine(ints)assertints(2,4,8)正如你所见你需要将is_tuple_fine固定装置作为参数传递给每个你想要使用的测试函数。然后它将变成你需要使用的函数。结论当你知道第三种方法时它看起来相当自然和地道。确实如此——因为这正是 Pytest 固定装置被设计的目的即从conftest.py文件传递对象到测试文件。当你不知道可以这样操作时自然就会想到导入辅助函数。有时候它会工作但有时候不会——你可能会花一些时间去思考发生了什么。没有发生任何事情。这就是 Pytest 的工作方式。现在你知道该怎么做我非常确信你不会在通过conftest.py文件传递辅助函数时遇到任何问题。有了这个工具Pytest 测试甚至更简单。