跳转到主要内容

Testscenarios,一个用于依赖注入的pyunit扩展

项目描述

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

根据用户的意愿,在Apache许可证版本2.0或BSD 3条款许可证下授权。两个许可证的副本都可在项目源代码中找到,分别为Apache-2.0和BSD。您不得使用此文件,除非符合这两个许可证之一。

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

testscenarios为Python unittest风格的测试提供清晰的依赖注入。这可用于接口测试(通过单个测试套件测试多个实现)或用于经典依赖注入(向测试代码本身外部提供具有依赖性的测试,便于在不同情况下进行测试)。

依赖项

为什么选择TestScenarios

标准Python unittest.py提供了在两个(或更多)场景下运行单个test_foo方法的明显方法:通过创建一个混入(mix-in),它提供了构成场景的函数、对象或设置。然而,这种方法有限且不够令人满意。首先,当两个项目合作进行测试套件开发时(例如,一个插件可能需要在它的实现上运行某个接口的标准测试),它们很容易失去同步:当混入的TestCase类列表发生变化时,插件将无法运行某些测试,或者尝试运行已删除的测试时出错。其次,处理运行时创建的子类(处理上述偏移的一种方法)并不容易,因为它们需要更多的间接定位测试的来源,并且常常被pyflakes、pylint等工具忽略。

testscenarios的意图是使在多个场景下动态运行单个测试变得清晰、易于调试,即使场景列表是动态生成的也是如此。

定义场景

一个“场景”是一个场景的字符串名称和描述该场景的字典参数的元组。名称附加到测试名称上,当测试运行时,参数将提供给测试实例。

场景以“场景列表”的形式呈现,通常是Python列表,但也可能是任何可迭代的对象。

应用场景

其核心概念很简单。对于具有场景列表的给定测试对象,我们为每个场景准备一个新的测试对象。这包括

  • 将测试克隆到具有唯一id的新测试。

  • 通过将场景中的每个键、值设置为测试对象的属性来将场景应用于测试。

要无缝实现这一点,存在一些复杂因素,这些因素分为两个领域

  • 选择要使用的场景。(请参阅为测试设置场景)

  • 实现乘法。

子类化

如果您可以子类化TestWithScenarios,则TestWithScenarios中的run()方法将负责测试乘法。在测试执行时,它将作为生成器导致多个测试执行。为了使此操作可靠,TestWithScenarios必须是MRO中的第一个类,并且您不能覆盖run()或__call__。这是最稳健的方法,因为任何遵循python unittest协议的测试运行器或测试加载器都将运行所有场景。

手动生成

如果您不能子类化TestWithScenarios(例如,因为您正在使用TwistedTestCase、TestCaseWithResources或任何其他有用的测试基类,或者需要自己覆盖run()或__call__)则可以通过调用testscenarios.generate_scenarios()来使场景应用延迟发生。例如

>>> import unittest
>>> try:
...     from StringIO import StringIO
... except ImportError:
...     from io import StringIO
>>> from testscenarios.scenarios import generate_scenarios

这可以与标准库中的加载器和运行器一起工作,或者可能是其他实现

>>> loader = unittest.TestLoader()
>>> test_suite = unittest.TestSuite()
>>> runner = unittest.TextTestRunner(stream=StringIO())

>>> mytests = loader.loadTestsFromNames(['doc.test_sample'])
>>> test_suite.addTests(generate_scenarios(mytests))
>>> runner.run(test_suite)
<unittest...TextTestResult run=1 errors=0 failures=0>

测试加载器

一些测试加载器支持诸如load_teststest_suite之类的钩子。确保您的测试通过这些钩子应用了场景是一个好主意 - 这意味着支持这些钩子(如nosetrialtribunal)的外部测试运行器仍然会运行您的场景。(当然,如果您使用的是子类化方法,这已经得到了保证)。使用load_tests

>>> def load_tests(standard_tests, module, loader):
...     result = loader.suiteClass()
...     result.addTests(generate_scenarios(standard_tests))
...     return result

作为便利,这可以在load_tests_apply_scenarios中找到,因此使用场景测试的模块只需说

>>> from testscenarios import load_tests_apply_scenarios as load_tests

Python 2.7及以上版本支持不同的load_tests调用约定 <https://bugs.launchpad.net/bzr/+bug/607412>。 load_tests_apply_scenarios可以处理这两种情况。

使用test_suite

>>> def test_suite():
...     loader = TestLoader()
...     tests = loader.loadTestsFromName(__name__)
...     result = loader.suiteClass()
...     result.addTests(generate_scenarios(tests))
...     return result

为测试设置场景

示例测试场景可以在doc/文件夹中找到。

详见 pydoc testscenarios 以获取详细信息。

关于TestCase

您可以在测试用例上设置一个场景属性

>>> class MyTest(unittest.TestCase):
...
...     scenarios = [
...         ('scenario1', dict(param=1)),
...         ('scenario2', dict(param=2)),]

这提供了找到特定测试场景的主要接口。子类将继承场景(除非它们覆盖了该属性)。

加载后

测试场景也可以在之后任意生成,只要测试尚未运行。只需替换(或更改,但请注意,许多测试可能共享单个场景属性)。例如,在这个例子中,一些第三方测试被扩展为使用自定义场景。

>>> import testtools
>>> class TestTransport:
...     """Hypothetical test case for bzrlib transport tests"""
...     pass
...
>>> stock_library_tests = unittest.TestLoader().loadTestsFromNames(
...     ['doc.test_sample'])
...
>>> for test in testtools.iterate_tests(stock_library_tests):
...     if isinstance(test, TestTransport):
...         test.scenarios = test.scenarios + [my_vfs_scenario]
...
>>> suite = unittest.TestSuite()
>>> suite.addTests(generate_scenarios(stock_library_tests))

生成的测试没有scenarios列表,因为它们通常不需要更多扩展。然而,您可以将scenarios列表添加回它们,然后通过generate_scenarios再次运行以生成测试的笛卡尔积。

>>> class CrossProductDemo(unittest.TestCase):
...     scenarios = [('scenario_0_0', {}),
...                  ('scenario_0_1', {})]
...     def test_foo(self):
...         return
...
>>> suite = unittest.TestSuite()
>>> suite.addTests(generate_scenarios(CrossProductDemo("test_foo")))
>>> for test in testtools.iterate_tests(suite):
...     test.scenarios = [
...         ('scenario_1_0', {}),
...         ('scenario_1_1', {})]
...
>>> suite2 = unittest.TestSuite()
>>> suite2.addTests(generate_scenarios(suite))
>>> print(suite2.countTestCases())
4

动态场景

一个常见的用例是让场景列表根据插件和可用库动态变化。一种简单的方法是在相关测试中使用的地方提供一个全局范围的场景,然后可以进行自定义,或者从注册表等动态填充场景。例如

>>> hash_scenarios = []
>>> try:
...     from hashlib import md5
... except ImportError:
...     pass
... else:
...     hash_scenarios.append(("md5", dict(hash=md5)))
>>> try:
...     from hashlib import sha1
... except ImportError:
...     pass
... else:
...     hash_scenarios.append(("sha1", dict(hash=sha1)))
...
>>> class TestHashContract(unittest.TestCase):
...
...     scenarios = hash_scenarios
...
>>> class TestHashPerformance(unittest.TestCase):
...
...     scenarios = hash_scenarios

强制场景

apply_scenarios函数可以将场景应用到没有任何场景的测试中。apply_scenariosgenerate_scenarios的工作马,除了它接受传递的场景而不是反查测试对象以确定场景。apply_scenarios函数不重置测试场景属性,允许在不影响现有场景选择的情况下分层场景。

生成场景

一些函数(目前有一个)可用于简化常见情况下场景列表的生成。

按实现模块进行测试

通常有多个Python模块提供相同的功能和接口,并且希望将相同的测试应用到所有这些模块上。

在某些情况下,并非所有静态定义的实现都能在特定的测试环境中使用。例如,可能有一个模块既有C语言实现也有纯Python实现。如果可以加载C模块,您希望对其进行测试,但如果C模块尚未编译,测试也应通过。

per_module_scenarios函数为每个命名的模块生成一个场景。导入的模块对象设置为结果的场景的提供属性名称。在导入期间引发ImportError的模块将使用异常的sys.exc_info()而不是模块对象。测试可以检查属性是否为元组来决定要做什么(例如跳过)。

请注意,为了使测试有效,对受测试模块的所有访问都必须通过测试对象的相应属性进行。如果其中一个实现也被测试模块或其他直接导入,testscenarios不会神奇地阻止它被使用。

编写场景的建议

如果参数化测试是因为错误而没有参数化运行的,它应该失败而不是使用默认值,因为这可能会隐藏错误。

产生场景

multiply_scenarios函数产生传递的场景的笛卡尔积

>>> from testscenarios.scenarios import multiply_scenarios
>>>
>>> scenarios = multiply_scenarios(
...      [('scenario1', dict(param1=1)), ('scenario2', dict(param1=2))],
...      [('scenario2', dict(param2=1))],
...      )
>>> scenarios == [('scenario1,scenario2', {'param2': 1, 'param1': 1}),
...               ('scenario2,scenario2', {'param2': 1, 'param1': 2})]
True

项目详情


下载文件

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

源代码分发

testscenarios-0.5.0.tar.gz (21.0 kB 查看散列值)

上传时间 源代码

构建分发

testscenarios-0.5.0-py2.py3-none-any.whl (21.0 kB 查看散列值)

上传时间 Python 2 Python 3

由支持