如何在 Python 中生成动态(参数化)单元测试?
我有某种测试数据,想为每个项目创建一个单元测试。 我的第一个想法是这样做:
import unittest
l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]
class TestSequence(unittest.TestCase):
def testsample(self):
for name, a,b in l:
print "test", name
self.assertEqual(a,b)
if __name__ == '__main__':
unittest.main()
这样做的缺点是它在一个测试中处理所有数据。 我想为每一项动态生成一个测试。 有什么建议么?
I have some kind of test data and want to create a unit test for each item. My first idea was to do it like this:
import unittest
l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]
class TestSequence(unittest.TestCase):
def testsample(self):
for name, a,b in l:
print "test", name
self.assertEqual(a,b)
if __name__ == '__main__':
unittest.main()
The downside of this is that it handles all data in one test. I would like to generate one test for each item on the fly. Any suggestions?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(25)
元编程很有趣,但它可能会造成阻碍。 这里的大多数解决方案都很难:
因此,我的第一个建议是遵循简单/显式路径(适用于任何测试运行程序):
因为我们不应该重复自己,我的第二个建议建立在哈维尔的回答< /a>:接受基于属性的测试。 假设库:
“在测试用例生成方面比我们人类更加狡猾”
将提供简单的计数示例
适用于任何测试运行程序
具有许多更有趣的功能(统计、附加测试输出...)
类 TestSequence(unittest.TestCase):
要测试您的特定示例,只需添加:
要仅运行一个特定示例,您可以注释掉其他示例(提供的示例)将首先运行)。 您可能需要使用
@given(st.nothing())
。 另一种选择是将整个块替换为:好的,您没有不同的测试名称。 但也许您只需要:
更有趣的例子
Meta-programming is fun, but it can get in the way. Most solutions here make it difficult to:
So, my first suggestion is to follow the simple/explicit path (works with any test runner):
Since we shouldn't repeat ourselves, my second suggestion builds on Javier's answer: embrace property based testing. Hypothesis library:
is "more relentlessly devious about test case generation than us mere humans"
will provide simple count-examples
works with any test runner
has many more interesting features (statistics, additional test output, ...)
class TestSequence(unittest.TestCase):
To test your specific examples, just add:
To run only one particular example, you can comment out the other examples (provided example will be run first). You may want to use
@given(st.nothing())
. Another option is to replace the whole block by:OK, you don't have distinct test names. But maybe you just need:
Funnier example
只需使用元类,如下所示;
输出:
Just use metaclasses, as seen here;
Output:
结果:
RESULT:
使用unittest(自3.4起)
自Python 3.4以来,标准库
unittest
包具有subTest
上下文管理器。请参阅文档:
示例:
您还可以为
subTest()
指定自定义消息和参数值:使用 nose
nose 测试框架支持此。
示例(下面的代码是包含测试的文件的全部内容):
nosetests 命令的输出:
Using unittest (since 3.4)
Since Python 3.4, the standard library
unittest
package has thesubTest
context manager.See the documentation:
Example:
You can also specify a custom message and parameter values to
subTest()
:Using nose
The nose testing framework supports this.
Example (the code below is the entire contents of the file containing the test):
The output of the nosetests command:
这可以使用元类优雅地解决:
This can be solved elegantly using Metaclasses:
从 Python 3.4 开始,unittest 已为此目的引入了子测试。 有关详细信息,请参阅文档。 TestCase.subTest 是一个上下文管理器,它允许隔离测试中的断言,以便在失败时报告参数信息,但它不会停止测试执行。 以下是文档中的示例:
测试运行的输出将是:
这也是 unittest2,因此它可用于早期版本的 Python。
As of Python 3.4, subtests have been introduced to unittest for this purpose. See the documentation for details. TestCase.subTest is a context manager which allows one to isolate asserts in a test so that a failure will be reported with parameter information, but it does not stop the test execution. Here's the example from the documentation:
The output of a test run would be:
This is also part of unittest2, so it is available for earlier versions of Python.
load_tests 是 2.7 中引入的一个鲜为人知的机制,用于动态创建 TestSuite。 有了它,您可以轻松创建参数化测试。
例如:
该代码将运行 load_tests 返回的 TestSuite 中的所有测试用例。 发现机制不会自动运行其他测试。
或者,您也可以使用继承,如下票证所示:http://bugs.python.org/msg151444
load_tests is a little known mechanism introduced in 2.7 to dynamically create a TestSuite. With it, you can easily create parametrized tests.
For example:
That code will run all the TestCases in the TestSuite returned by load_tests. No other tests are automatically run by the discovery mechanism.
Alternatively, you can also use inheritance as shown in this ticket: http://bugs.python.org/msg151444
前几天我在查看源代码时遇到了 ParamUnittest对于 氡 (GitHub 存储库上的示例用法)。 它应该与扩展 TestCase 的其他框架(如 Nose)一起使用。
这是一个例子:
I came across ParamUnittest the other day when looking at the source code for radon (example usage on the GitHub repository). It should work with other frameworks that extend TestCase (like Nose).
Here is an example:
我发现这很适合我的目的,特别是当我需要生成对数据集合执行稍微不同的处理的测试时。
TestGenerator
类可用于生成不同的测试用例集,例如TestCluster
。TestCluster
可以被认为是TestGenerator
接口的实现。I have found that this works well for my purposes, especially if I need to generate tests that do slightly difference processes on a collection of data.
The
TestGenerator
class can be used to spawn different sets of test cases likeTestCluster
.TestCluster
can be thought of as an implementation of theTestGenerator
interface.我在使用一种非常特殊的参数化测试风格时遇到了麻烦。 我们所有的 Selenium 测试都可以在本地运行,但它们也应该能够在 SauceLabs 上的多个平台上远程运行。 基本上,我想采用大量已经编写的测试用例,并用尽可能少的代码更改来参数化它们。 此外,我需要能够将参数传递到 setUp 方法中,这是我在其他地方没有看到的任何解决方案。
这就是我的想法:
有了这个,我所要做的就是向每个常规的旧测试用例添加一个简单的装饰器 @sauce_labs() ,现在运行它们时,它们被包装并重写,以便所有测试方法被参数化和重命名。 LoginTests.test_login(self) 以 LoginTests.test_login_internet_explorer_10.0(self)、LoginTests.test_login_internet_explorer_11.0(self) 和 LoginTests.test_login_firefox_43.0(self) 运行,并且每个都有参数 self.platform 来决定使用哪个浏览器/运行的平台,甚至在 LoginTests.setUp 中,这对我的任务至关重要,因为这是初始化与 SauceLabs 的连接的地方。
无论如何,我希望这对那些想要对其测试进行类似“全局”参数化的人有所帮助!
I'd been having trouble with a very particular style of parameterized tests. All our Selenium tests can run locally, but they also should be able to be run remotely against several platforms on SauceLabs. Basically, I wanted to take a large amount of already-written test cases and parameterize them with the fewest changes to code possible. Furthermore, I needed to be able to pass the parameters into the setUp method, something which I haven't seen any solutions for elsewhere.
Here's what I've come up with:
With this, all I had to do was add a simple decorator @sauce_labs() to each regular old TestCase, and now when running them, they're wrapped up and rewritten, so that all the test methods are parameterized and renamed. LoginTests.test_login(self) runs as LoginTests.test_login_internet_explorer_10.0(self), LoginTests.test_login_internet_explorer_11.0(self), and LoginTests.test_login_firefox_43.0(self), and each one has the parameter self.platform to decide what browser/platform to run against, even in LoginTests.setUp, which is crucial for my task since that's where the connection to SauceLabs is initialized.
Anyway, I hope this might be of help to someone looking to do a similar "global" parameterization of their tests!
您可以使用
TestSuite
和自定义TestCase
类。You can use
TestSuite
and customTestCase
classes.我使用元类和装饰器来生成测试。 您可以检查我的实现 python_wrap_cases。 该库不需要任何测试框架。
您的示例:
控制台输出:
您也可以使用生成器。 例如,此代码使用参数
a__list
和b__list
生成所有可能的测试组合控制台输出:
I use metaclasses and decorators for generate tests. You can check my implementation python_wrap_cases. This library doesn't require any test frameworks.
Your example:
Console output:
Also you may use generators. For example this code generate all possible combinations of tests with arguments
a__list
andb__list
Console output:
可以使用 pytest 来完成。 只需编写包含以下内容的文件
test_me.py
:并使用命令
py.test --tb=short test_me.py
运行测试。 然后输出将如下所示:很简单! 此外 pytest 还有更多功能,例如
fixtures
、mark
、断言
等It can be done by using pytest. Just write the file
test_me.py
with content:And run your test with command
py.test --tb=short test_me.py
. Then the output will look like:It is simple! Also pytest has more features like
fixtures
,mark
,assert
, etc.您可以使用 nose-ittr 插件 (
pip install noose-ittr
)。与现有测试集成非常容易,并且需要最少的更改(如果有)。 它还支持nose多处理插件。
请注意,您还可以为每个测试提供自定义
setup
函数。还可以传递
nosetest
参数,就像使用其内置插件attrib
一样。 这样您就可以仅运行具有特定参数的特定测试:You can use the nose-ittr plugin (
pip install nose-ittr
).It's very easy to integrate with existing tests, and minimal changes (if any) are required. It also supports the nose multiprocessing plugin.
Note that you can also have a customize
setup
function per test.It is also possible to pass
nosetest
parameters like with their built-in pluginattrib
. This way you can run only a specific test with specific parameter:还有 Hypothesis 添加模糊或基于属性的测试。
这是一种非常强大的测试方法。
There's also Hypothesis which adds fuzz or property based testing.
This is a very powerful testing method.
这实际上与前面的答案中提到的参数化相同,但特定于单元测试:
示例用法:
This is effectively the same as
parameterized
as mentioned in a previous answer, but specific tounittest
:Example usage:
尝试 TestScenarios 库会让您受益匪浅。
You would benefit from trying the TestScenarios library.
我在使这些工作适用于
setUpClass
时遇到了麻烦。这是 Javier 的答案 的一个版本,它允许
setUpClass
访问动态分配的属性。输出
I had trouble making these work for
setUpClass
.Here's a version of Javier's answer that gives
setUpClass
access to dynamically allocated attributes.Outputs
基于元类的答案在 Python 3 中仍然有效,但必须使用
metaclass
参数,而不是__metaclass__
属性,如下所示:The metaclass-based answers still work in Python 3, but instead of the
__metaclass__
attribute, one has to use themetaclass
parameter, as in:以下是我的解决方案。 我发现这在以下情况下很有用:
应该适用于unittest.Testcase和unittest discovery
有一组针对不同参数设置运行的测试。
非常简单,不依赖其他包
Following is my solution. I find this useful when:
Should work for unittest.Testcase and unittest discover
Have a set of tests to be run for different parameter settings.
Very simple and no dependency on other packages
除了使用 setattr 之外,我们还可以在 Python 3.2 及更高版本中使用 load_tests。
Besides using setattr, we can use load_tests with Python 3.2 and later.
此解决方案适用于 Python 2 和 Python 3 的
unittest
和nose
:This solution works with
unittest
andnose
for Python 2 and Python 3:使用 ddt 库。 它为测试方法添加了简单的装饰器:
该库可以通过
pip
安装。 它不需要nose
,并且与标准库unittest
模块配合得很好。Use the ddt library. It adds simple decorators for the test methods:
This library can be installed with
pip
. It doesn't requirenose
, and works excellent with the standard libraryunittest
module.这称为“参数化”。
有多种工具支持这种方法。 例如:
结果代码如下所示:
它将生成测试:
由于历史原因,我将保留 2008 年左右的原始答案):
我使用类似这样的代码:
This is called "parametrization".
There are several tools that support this approach. E.g.:
The resulting code looks like this:
Which will generate the tests:
For historical reasons I'll leave the original answer circa 2008):
I use something like this: