【问题标题】:Separate test cases per input files?每个输入文件单独的测试用例?
【发布时间】:2017-08-18 08:05:58
【问题描述】:

大多数测试框架都假定“1 个测试 = 1 个 Python 方法/函数”, 并在函数执行时认为测试通过 提出断言。

我正在测试一个类似编译器的程序(一个读取 *.foo 的程序 文件并处理它们的内容),为此我想对许多输入(*.foo)文件执行相同的测试。 IOW,我的测试看起来像:

class Test(unittest.TestCase):
    def one_file(self, filename):
        # do the actual test

    def list_testcases(self):
        # essentially os.listdir('tests/') and filter *.foo files.

    def test_all(self):
        for f in self.list_testcases():
            one_file(f)

我当前的代码使用 unittest 来自 Python的标准库,即one_file使用self.assert...(...) 检查测试是否通过的语句。

这很有效,因为我确实得到了一个成功/失败的程序 当我的代码正常/错误时,但我失去了很多优势 测试框架:

  • 我没有得到像“Y 测试中有 X 次失败”这样的相关报告,也没有 通过/失败的测试列表。 (我打算使用这样的系统 不仅要测试我自己的开发,还要给学生的代码评分 作为一名教师,所以报告对我来说很重要)

  • 我没有获得测试独立性。第二个测试在 先离开环境,以此类推。第一次失败停止 测试套件:失败后的测试用例根本不会运行。

  • 我觉得我在滥用我的测试框架:只有 一个测试功能,因此单元测试声音的自动测试发现 例如矫枉过正。可以(应该?)编写相同的代码 带有基本 assert 的普通 Python。

一个明显的替代方法是将我的代码更改为类似

class Test(unittest.TestCase):
    def one_file(self, filename):
        # do the actual test

    def test_file1(self):
        one_file("first-testcase.foo")

    def test_file2(self):
        one_file("second-testcase.foo")

然后我得到了 unittest 的所有优点,但是:

  • 还有很多代码要写。

  • “忘记”一个测试用例很容易,即在 tests/ 忘记将其添加到 Python 测试中。

我可以想象一个解决方案,我会动态地为每个测试用例生成一个方法(沿着setattr(self, 'test_file' + str(n), ...) 的行),以生成第二个解决方案的代码,而无需手动编写它。但是对于一个看起来并不复杂的用例来说,这听起来确实有点过头了。

我怎样才能得到最好的,即 自动测试用例发现(列出tests/*.foo 文件),测试 独立性和适当的报告?

【问题讨论】:

  • 你可以看看pythonhosted.org/behave。它包含强大的参数化功能。
  • 感谢您的提示,但我不明白这将如何解决我的问题(虽然我很可能错过了一些东西......)。从本质上讲,behave 允许我编写自然语言而不是 Python,Scenario Outlines 允许分解代码(有点像我上面的 one_file 函数),但测试套件仍然需要明确列出所有测试文件,对吧?
  • 关于行为:另外,在这种情况下,我更喜欢直接编写 Python 代码而不是自然语言。无论如何,再次感谢您的建议,我正在寻找思想的食物,就像我正在寻找真正的解决方案一样;-)。

标签: python unit-testing testing


【解决方案1】:

如果您可以使用pytest 作为测试运行器,那么使用parametrize decorator 实际上非常简单:

import pytest, glob

all_files = glob.glob('some/path/*.foo')

@pytest.mark.parametrize('filename', all_files)
def test_one_file(filename):
    # do the actual test

这也会以一种有用的方式自动命名测试,以便您查看哪些文件失败了:

$ py.test
================================== test session starts ===================================
platform darwin -- Python 3.6.1, pytest-3.1.3, py-1.4.34, pluggy-0.4.0
[...]
======================================== FAILURES ========================================
_____________________________ test_one_file[some/path/a.foo] _____________________________

filename = 'some/path/a.foo'

    @pytest.mark.parametrize('filename', all_files)
    def test_one_file(filename):
>      assert False
E      assert False

test_it.py:7: AssertionError
_____________________________ test_one_file[some/path/b.foo] _____________________________

filename = 'some/path/b.foo'

    @pytest.mark.parametrize('filename', all_files)
    def test_one_file(filename):
[...]

【讨论】:

  • 太好了,谢谢。本质上,我缺少的关键字是“参数化”(嗯,在我的情况下是动态参数化),用它谷歌搜索我发现stackoverflow.com/questions/32899/… 这基本上是同一个问题。
【解决方案2】:

这里有一个解决方案,虽然它可能被认为不是很漂亮...想法是动态创建新函数,将它们添加到测试类中,并使用函数名称作为参数(例如,文件名):

# import
import unittest

# test class
class Test(unittest.TestCase):

    # example test case
    def test_default(self):
        print('test_default')
        self.assertEqual(2,2)

# set string for creating new function    
func_string="""def test(cls):

        # get function name and use it to pass information
        filename = inspect.stack()[0][3]

        # print function name for demonstration purposes
        print(filename)

        # dummy test for demonstration purposes
        cls.assertEqual(type(filename),str)"""

# add new test for each item in list
for f in ['test_bla','test_blu','test_bli']:

    # set name of new function
    name=func_string.replace('test',f)

    # create new function
    exec(name)

    # add new function to test class
    setattr(Test, f, eval(f))

if __name__ == "__main__":
    unittest.main()

这会正确运行所有四个测试并返回:

> test_bla
> test_bli
> test_blu
> test_default
> Ran 4 tests in 0.040s
> OK

【讨论】:

  • 感谢您的回答。这实际上就是我所说的“我可以想象一个解决方案,我可以动态地为每个测试用例生成一个方法......”。有效,但对我来说似乎真的有点矫枉过正。 Freddie 对parametrize 的回答似乎对我来说是正确的。
猜你喜欢
  • 2020-08-05
  • 2020-01-02
  • 2021-10-25
  • 1970-01-01
  • 2016-11-07
  • 2018-05-01
  • 2016-11-29
  • 2016-01-20
  • 1970-01-01
相关资源
最近更新 更多