用Python生成py.test测试

23

如果您感兴趣,问题在前,解释在后。

在py.test上下文中,如何从少量的测试函数模板生成大量的测试函数?

类似于:

models = [model1,model2,model3]
data_sets = [data1,data2,data3]

def generate_test_learn_parameter_function(model,data):
    def this_test(model,data):
        param = model.learn_parameters(data)
        assert((param - model.param) < 0.1 )
    return this_test

for model,data in zip(models,data_sets):
    # how can py.test can see the results of this function?
    generate_test_learn_parameter_function(model,data)

解释:

我编写的代码需要一个模型结构,一些数据,并学习模型的参数。因此,我的单元测试包括一堆模型结构和预生成的数据集,然后是每个结构+数据上完成的大约5个机器学习任务。

因此,如果我手动编码,我需要每个模型每个任务一个测试。每次我想出一个新模型时,我需要复制并粘贴这5个任务,更改我指向的pickled结构+数据。这让我觉得很不好。理想情况下,我希望有5个模板函数来定义我的5个任务,然后只需为我指定的结构列表输出测试函数。

搜索引擎告诉我可以使用a)工厂或b)闭包来解决这个问题,但两者都让我感到困惑,并认为一定有更简单的方法,因为这个问题必须经常面对真正的程序员。那么有没有呢?


编辑:以下是如何解决此问题!

def pytest_generate_tests(metafunc):
    if "model" in metafunc.funcargnames:
        models = [model1,model2,model3]
        for model in models:
            metafunc.addcall(funcargs=dict(model=model))

def test_awesome(model):
    assert model == "awesome"
这将对我清单中的每个模型应用“test_awesome”测试!感谢 @dfichter!(注意:顺便说一句,那个assert语句总是通过的)

通常动态生成测试代码是个不好的主意,因为你还得为测试代码编写测试等。我认为这表明,与其生成新代码或复制粘贴,你可以找到函数可以测试的共性,而不必确切知道它们正在测试什么。请复制并粘贴这5个任务。 - Falmarri
2
目前我正在编写像 def test_learn: 这样的测试代码,对于每个模型都是 for model in models: assert(error < threshold)。但这意味着测试可能会在任何一个模型上失败,而我希望有一个测试用例只对一个模型失败,并且另一个相同的机器学习函数的测试用例能够通过。 - Mike Dewar
3
请尽量避免“复制粘贴编程”,具体信息请参考维基百科上的相关条目:http://en.wikipedia.org/wiki/Copy_and_paste_programming。 - László Papp
3个回答

20

良好的直觉。 py.test 使用其 pytest_generate_tests() 钩子完全支持您所说的内容。他们在这里进行了解释。


太棒了。我将编辑我的问题,在最后添加我所做的内容,以防有人在这里寻找类似的东西。 - Mike Dewar
链接已经失效了。它还有支持吗? - asaf92
1
我认为这就是它:https://docs.pytest.org/en/latest/how-to/parametrize.html#basic-pytest-generate-tests-example - delucasvb

8
您也可以使用参数化的Fixture来实现。虽然Hooks是构建Py.test插件的API,但参数化Fixture是一种通用的方式,可以生成多个值的Fixture,并为它们生成额外的测试用例。
插件旨在成为一些项目范围(或包范围)的功能,而参数化Fixture正是需要将某些资源参数化为测试用例的功能。
因此,您的解决方案可以重写为:
@pytest.fixture(params=[model1, model2, model3])
def model(request):
    return request.param

def test_awesome(model):
    assert model == "awesome"

1

今天最简单的解决方案是使用pytest.mark.parametrize

@pytest.mark.parametrize('model', ["awesome1", "awesome2", "awesome3"])
def test_awesome(model):
    assert model.startswith("awesome")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接