跳转到主要内容

Fixtures,用于编写清晰测试和更多可重复状态。

项目描述

fixtures 定义了一个用于可重用状态/支持逻辑的 Python 合同,主要用于单元测试。包含辅助和适配逻辑,使您能够轻松地使用 fixtures 合同编写自己的 fixtures。提供粘合代码,使得在 unittest 兼容的测试用例中使用符合 Fixtures 合同的 fixtures 变得简单直接。

依赖项

  • Python 3.7+ 这是 fixtures 编写和运行的基础语言。

  • pbr 用于 fixtures 的版本和发布管理。

添加 fixtures[streams] 额外功能

  • testtools <https://launchpad.net/testtools>

    testtools 提供了用于报告有关 fixtures(无论是在测试或生产环境中使用)信息的 API 的有用粘合函数。

要使用包含的粘合函数在单元测试套件中使用,需要一个支持 TestCase.addCleanup 的测试环境。编写自己的粘合代码很容易。或者,您可以直接使用 fixtures 而不使用任何支持代码。

要运行 fixtures 的测试套件,需要 testtools

为什么使用 fixtures

标准的 Python unittest 没有提供明显的创建和重用测试用例所需状态的方法,除了在测试类上添加方法。这种方法扩展性差 - 在这样做时,将复杂的辅助函数传播到测试类层次结构是一种常见模式。虽然模拟是一个很好的工具,但它本身并不能阻止这种情况(如果放在测试类上,模拟复杂事物的辅助函数也可能以同样的方式积累)。

通过定义一个统一的合同,其中辅助函数不依赖于测试类,我们可以允许所有常规的代码卫生活动在没有受到建模不同事物的类层次结构扭曲影响的情况下进行,这正是 TestCase 上的辅助函数所遭受的。

关于 fixtures

一个 fixture 表示某种状态。每个 fixture 都有一些特定于 fixture 的属性。例如,表示可以用于临时文件的目录的 fixture 可能有一个属性 path

大多数 fixture 都有完整的 pydoc 文档,因此请务必检查 pydoc fixtures 以获取使用信息。

创建 fixtures

至少,从 Fixture 继承,定义 _setUp 以初始化您的状态,在调用 cleanUp 时安排清理,然后您就完成了。

>>> import unittest
>>> import fixtures
>>> class NoddyFixture(fixtures.Fixture):
...     def _setUp(self):
...         self.frobnozzle = 42
...         self.addCleanup(delattr, self, 'frobnozzle')

这将初始化 frobnozzle 当调用 setUp 时,当调用 cleanUp 时,删除 frobnozzle 属性。在版本 1.3.0 之前,fixtures 建议重写 setUp。这仍然被支持,但由于以这种方式编写的无泄漏 fixtures 更难编写,因此不建议这样做。

如果您的设备有诊断数据,例如应用程序服务器的日志文件或日志消息,可以通过创建内容对象(testtools.content.Content)并调用 addDetail 来公开这些数据。

>>> from testtools.content import text_content
>>> class WithLog(fixtures.Fixture):
...     def _setUp(self):
...         self.addDetail('message', text_content('foo bar baz'))

useFixture 方法将使用另一个设备,调用它的 setUp 方法,调用 self.addCleanup(thefixture.cleanUp),附加任何细节并返回设备。这允许简单地组合不同的设备。

>>> class ReusingFixture(fixtures.Fixture):
...     def _setUp(self):
...         self.noddy = self.useFixture(NoddyFixture())

有一个辅助工具可以将函数或函数对适配为设备。它将函数的结果放入 fn_result 中。

>>> import os.path
>>> import shutil
>>> import tempfile
>>> def setup_function():
...     return tempfile.mkdtemp()
>>> def teardown_function(fixture):
...     shutil.rmtree(fixture)
>>> fixture = fixtures.FunctionFixture(setup_function, teardown_function)
>>> fixture.setUp()
>>> print (os.path.isdir(fixture.fn_result))
True
>>> fixture.cleanUp()

这甚至可以表达得更简洁。

>>> fixture = fixtures.FunctionFixture(tempfile.mkdtemp, shutil.rmtree)
>>> fixture.setUp()
>>> print (os.path.isdir(fixture.fn_result))
True
>>> fixture.cleanUp()

另一个变体是 MethodFixture,它用于将其他设备实现适配到设备中。

>>> class MyServer:
...    def start(self):
...        pass
...    def stop(self):
...        pass
>>> server = MyServer()
>>> fixture = fixtures.MethodFixture(server, server.start, server.stop)

您还可以使用 CompoundFixture 组合现有的设备。

>>> noddy_with_log = fixtures.CompoundFixture([NoddyFixture(),
...                                            WithLog()])
>>> with noddy_with_log as x:
...     print (x.fixtures[0].frobnozzle)
42

设备 API

上面的例子介绍了一些 Fixture API。为了在使用设备后能够进行清理,所有设备都定义了一个 cleanUp 方法,当设备完成时应该调用它。

由于在使用之前能够预先构建一组相关的设备很方便,设备还有一个 setUp 方法,在使用之前应该调用它。

对于创建成本较高的设备,一个常见的愿望是在多个测试用例中重复使用它们;为了支持这一点,基础 Fixture 还定义了一个 reset,它调用 self.cleanUp(); self.setUp()。能够更有效地使自身可重复使用的设备应该覆盖此方法。然后可以通过诸如 testresourcessetUpClasssetUpModule 等方式与多个测试状态一起使用。

当使用设备与测试时,您可以手动调用 setUpcleanUp 方法。不过,更方便的是使用来自 fixtures.TestWithFixtures 的内置胶水,它提供了一个定义 useFixture 方法(驼峰式命名法,因为整个 unittest 都是驼峰式命名法)的混合。它将在设备上调用 setUp,调用 self.addCleanup(fixture) 以安排清理,并返回设备。这允许编写:

>>> import testtools
>>> import unittest

请注意,我们使用 testtools.TestCase。因为 testtools 有它自己的 useFixture 实现,所以不需要在 testtools.TestCase 中使用 fixtures.TestWithFixtures

>>> class NoddyTest(testtools.TestCase, fixtures.TestWithFixtures):
...     def test_example(self):
...         fixture = self.useFixture(NoddyFixture())
...         self.assertEqual(42, fixture.frobnozzle)
>>> result = unittest.TestResult()
>>> _ = NoddyTest('test_example').run(result)
>>> print (result.wasSuccessful())
True

设备实现了上下文协议,因此您还可以将设备用作上下文管理器。

>>> with fixtures.FunctionFixture(setup_function, teardown_function) as fixture:
...    print (os.path.isdir(fixture.fn_result))
True

当多个清理操作出错时,fixture.cleanUp() 将引发一个包装异常,而不是选择一个任意的单个异常来引发。

>>> import sys
>>> from fixtures.fixture import MultipleExceptions
>>> class BrokenFixture(fixtures.Fixture):
...     def _setUp(self):
...         self.addCleanup(lambda:1/0)
...         self.addCleanup(lambda:1/0)
>>> fixture = BrokenFixture()
>>> fixture.setUp()
>>> try:
...    fixture.cleanUp()
... except MultipleExceptions:
...    exc_info = sys.exc_info()
>>> print (exc_info[1].args[0][0].__name__)
ZeroDivisionError

设备通常会公开有用的诊断细节,可用于追踪问题。 getDetails 方法将返回一个包含所有附加细节的字典,但只能在调用 cleanUp 之前调用。每个细节对象都是 testtools.content.Content 的实例。

>>> with WithLog() as l:
...     print(l.getDetails()['message'].as_text())
foo bar baz

setUp 中的错误

上述示例使用了 _setUp 而不是 setUp,因为基类的 setUp 实现旨在降低在从 _setUp 中引发错误时泄漏外部资源的机会。具体来说,setUp 包含一个 try/except 块,该块捕获所有异常,捕获任何已注册的详细对象,并在传播错误之前调用 self.cleanUp。只要您在调用可能失败的代码之前注册任何清理操作,这就会导致它们被清理。捕获的详细对象将提供给引发异常的参数。

如果发生的错误是 Exception 的子类,则 setUp 将引发包含包含详细对象的 SetupErrorMultipleExceptions。否则,为了防止在调用层不恰当地捕获通常不可捕获的错误,如 KeyboardInterrupt,原始异常将原样引发,并且除了原始异常之外没有其他诊断数据可用。

共享依赖

在复杂环境中,一个常见的用例是某些固定装置被其他装置共享。

考虑使用 TempDir 和在其之上构建的两个固定装置进行测试的情况;比如说一个小型数据库和一个网络服务器。编写其中任何一个都几乎是微不足道的。然而,正确处理 reset() 是困难的:数据库和网络服务器都合理地期望能够在临时目录被删除之前丢弃它们在临时目录中可能打开的操作系统资源。递归的 reset() 实现对一个工作,但对另一个则不行。在每次测试之间对 TempDir 实例调用 reset() 可能是可取的,但我们不想必须对较高层级的固定装置进行完整的 cleanUp(这将使 TempDir 无用并且可以轻易重置。我们有几个可用的选项。

想象一下,网络服务器在没有任何方式上依赖于数据库固定装置 - 我们只想让网络服务器和数据库固定装置共存于同一个临时目录中。

一个简单的选项是为较高层级的固定装置提供一个显式的依赖固定装置以供使用。这把复杂性从核心推到了固定装置的用户身上

>>> class WithDep(fixtures.Fixture):
...     def __init__(self, tempdir, dependency_fixture):
...         super(WithDep, self).__init__()
...         self.tempdir = tempdir
...         self.dependency_fixture = dependency_fixture
...     def setUp(self):
...         super(WithDep, self).setUp()
...         self.addCleanup(self.dependency_fixture.cleanUp)
...         self.dependency_fixture.setUp()
...         # we assume that at this point self.tempdir is usable.
>>> DB = WithDep
>>> WebServer = WithDep
>>> tempdir = fixtures.TempDir()
>>> db = DB(tempdir, tempdir)
>>> server = WebServer(tempdir, db)
>>> server.setUp()
>>> server.cleanUp()

另一个选项是编写固定装置以优雅地处理在其下方重置的依赖项。如果固定装置会阻止依赖项重置(例如,通过在临时目录中保持文件锁打开 - 在 Windows 上,这将阻止目录被删除),则这不足以解决问题。

另一种方法是,在重置固定装置之前为每个固定装置的用户发出某种类型的信号。在这个例子中,TempDir 可能会提供一个订阅者属性,数据库和网络服务器都会在其中注册。在临时目录上调用 resetcleanUp 将触发所有订阅者的回调;数据库和网络服务器重置方法可能看起来像这样

>>> def reset(self):
...     if not self._cleaned:
...         self._clean()

(它们对临时目录回调的操作是执行所需的工作并设置 self._cleaned。)这种方法(可能)具有出人意料的效果,即重置网络服务器可能会重置数据库 - 如果网络服务器依赖于 tempdir.reset 作为重置网络服务器状态的方法。

另一种尚未实现的方案是提供一个依赖对象图和重置机制,它可以遍历该图,以及“重置开始”和“重置完成”之间的分离——数据库和Web服务器都会调用其 reset_starting 方法,然后重置tempdir,最后数据库和Web服务器将调用 reset_finishing

股票配置

除了 FixtureFunctionFixtureMethodFixture 类之外,配置还包括一些预定义的配置。配置的API文档将列出这些配置的完整集合,如果文档过时或不在手头,请参阅API文档。有关每个配置的完整功能集,请参阅API文档。

字节流

一个简单的适配器,用于将 BytesIO(尽管它可能在未来自动溢出到磁盘以处理大量内容)公开为详细信息对象,以便在测试失败描述中自动包含。与 MonkeyPatch 结合使用非常有用。

>>> fixture = fixtures.StringStream('my-content')
>>> fixture.setUp()
>>> with fixtures.MonkeyPatch('sys.something', fixture.stream):
...     pass
>>> fixture.cleanUp()

这需要 fixtures[streams] 额外组件。

环境变量

将您的代码与环境变量隔离开来,删除它们或将它们设置为新的值

>>> fixture = fixtures.EnvironmentVariable('HOME')

模拟记录器

将您的代码与外部日志配置隔离开来,这样您的测试就可以获取日志消息的输出,但它们不会输出到例如控制台

>>> fixture = fixtures.FakeLogger()

模拟Popen

模拟运行外部命令,而不是需要它来运行测试

>>> from io import BytesIO
>>> fixture = fixtures.FakePopen(lambda _:{'stdout': BytesIO('foobar')})

日志处理程序

替换或扩展记录器的处理程序。此配置项的行为取决于 nuke_handlers 参数的值:如果为 true,则删除记录器现有的处理程序并替换为提供的处理程序;如果为 false,则通过提供的处理程序扩展记录器的处理程序集合

>>> from logging import StreamHandler
>>> fixture = fixtures.LogHandler(StreamHandler())

模拟对象修补程序

mock.patch.object 适配为配置项

>>> class Fred:
...     value = 1
>>> fixture = fixtures.MockPatchObject(Fred, 'value', 2)
>>> with fixture:
...     Fred().value
2
>>> Fred().value
1

模拟修补程序

mock.patch 适配为配置项

>>> fixture = fixtures.MockPatch('subprocess.Popen.returncode', 3)

模拟修补程序多

mock.patch.multiple 适配为 fixture

>>> fixture = fixtures.MockPatchMultiple('subprocess.Popen', returncode=3)

猴子修补程序

控制命名Python属性的值

>>> def fake_open(path, mode):
...     pass
>>> fixture = fixtures.MonkeyPatch('__builtin__.open', fake_open)

请注意,修补方法时存在一些复杂性 - 请参阅API文档以获取详细信息。

嵌套临时文件

更改 tempfile 模块放置临时文件和目录的默认目录。这对于包含代码产生的噪音很有用,该代码未清理其临时文件。这不会影响提供显式包含目录的临时文件创建

>>> fixture = fixtures.NestedTempfile()

包路径条目

将单个目录添加到现有Python包的路径中。这添加到 package.__path__ 列表。如果目录已在路径中,则不会发生任何操作;如果没有,则在其 setUp 时添加,并在 cleanUp 时删除

>>> fixture = fixtures.PackagePathEntry('package/name', '/foo/bar')

Python包

创建Python包目录。特别适用于测试动态加载包/模块的代码,或模拟Python程序的命令行入口点

>>> fixture = fixtures.PythonPackage('foo.bar', [('quux.py', '')])

Python路径条目

将单个目录添加到 sys.path。如果目录已在路径中,则不会发生任何操作;如果没有,则在其 setUp 时添加,并在 cleanUp 时删除

>>> fixture = fixtures.PythonPathEntry('/foo/bar')

一个简单的适配器,用于将文件对象公开为详细信息对象,以便在测试失败描述中自动包含。提供了 StringStreamBytesStream 作为此配置项的具体用户。

这需要 fixtures[streams] 额外组件。

字符串流

一个简单的适配器,将(虽然未来可能会自动将大量内容写入磁盘)暴露为一个细节对象,以便自动包含在测试失败描述中。与结合使用非常有用。

>>> fixture = fixtures.StringStream('stdout')
>>> fixture.setUp()
>>> with fixtures.MonkeyPatch('sys.stdout', fixture.stream):
...     pass
>>> fixture.cleanUp()

这需要 fixtures[streams] 额外组件。

TempDir

创建一个临时目录并在之后清理它

>>> fixture = fixtures.TempDir()

之后,创建的目录存储在 fixtures 的 属性中。

TempHomeDir

创建一个临时目录并将其设置为环境中的 $HOME

>>> fixture = fixtures.TempHomeDir()

之后,创建的目录存储在 fixtures 的 属性中。

现在环境中 $HOME 将设置为相同的路径,并在 tearDown 后恢复到之前值。

Timeout

如果被覆盖的代码执行时间超过指定的整秒数,则终止。

有两种可能性,由 gentle 参数控制:当 gentle 时,将引发异常并使测试(或其它被覆盖的代码)失败。当不 gentle 时,整个过程将终止,这不太整洁,但更有可能打破没有 Python 代码运行的挂起。

WarningsCapture

捕获警告以供后续分析

>>> fixture = fixtures.WarningsCapture()

捕获的警告存储在 fixtures 的 captures 属性中,在 setUp 之后。

WarningsFilter

在测试运行期间配置警告过滤器

>>> fixture = fixtures.WarningsFilter(
...     [
...         {
...             'action': 'ignore',
...             'message': 'foo',
...             'category': DeprecationWarning,
...         },
...     ]
... )

顺序很重要:列表前面的条目会覆盖列表后面的条目,如果两者都匹配特定的警告。

贡献

Fixtures 的项目主页在 GitHub <https://github.com/testing-cabal/fixtures>。

许可证

版权(c)2010,Robert Collins <robertc@robertcollins.net>

用户可选择以下任一许可:Apache License,Version 2.0 或 BSD 3-clause 许可证。两个许可证的副本都可在项目源中找到,分别为 Apache-2.0 和 BSD。您不得使用此文件,除非遵守这两种许可证之一。

除非适用法律要求或书面同意,否则在这些许可证下分发的软件将按“现状”基础分发,不提供任何明示或暗示的保证或条件。请参阅您选择的许可证,以了解该许可证下对该许可证管理权限和限制的具体语言。

项目详情


下载文件

下载适用于您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源分发

fixtures-4.1.0.tar.gz (58.6 kB 查看哈希值)

上传时间: 源代码

构建分发版

fixtures-4.1.0-py3-none-any.whl (64.5 kB 查看哈希值)

上传时间: Python 3

由以下支持