如何多次运行同一TestSuite单元测试文本

时间:2019-01-20 16:49:51

标签: python python-3.x

我想对运行一次的产品的多个版本进行测试。这是代码示例:

import unittest

suite = unittest.TestLoader().discover("./tests")
runner = unittest.TextTestRunner()

for build in [build1, build2]:
    get_the_build(build)
    runner.run(suite)

第一次迭代效果很好,但是在第二次迭代的开始出现了一个错误:

Traceback (most recent call last):
  File "D:/Path/to/my/folder/run_tests.py", line 9, in <module>
    runner.run(suite)
  File "C:\Program Files (x86)\Python36-32\lib\unittest\runner.py", line 176, in run
    test(result)
  File "C:\Program Files (x86)\Python36-32\lib\unittest\suite.py", line 84, in __call__
    return self.run(*args, **kwds)
  File "C:\Program Files (x86)\Python36-32\lib\unittest\suite.py", line 122, in run
    test(result)
TypeError: 'NoneType' object is not callable

发生了什么事? runner发出什么结果?为什么会失败?有什么想法可以解决问题吗?

2 个答案:

答案 0 :(得分:2)

好吧,好吧。我花了生命的最后一个小时来查看GitHub中unittest的代码,可以在here中找到它们。我刚去过suite.pyhere)的代码,这是您遇到的错误文件之一。这是TestSuite.run的实际代码:

def run(self, result, debug=False):
    topLevel = False
    if getattr(result, '_testRunEntered', False) is False:
        result._testRunEntered = topLevel = True

    for index, test in enumerate(self):
        if result.shouldStop:
            break

        if _isnotsuite(test):
            self._tearDownPreviousClass(test, result)
            self._handleModuleFixture(test, result)
            self._handleClassSetUp(test, result)
            result._previousTestClass = test.__class__

            if (getattr(test.__class__, '_classSetupFailed', False) or
                getattr(result, '_moduleSetUpFailed', False)):
                continue

        if not debug:
            test(result)
        else:
            test.debug()

        if self._cleanup:
            self._removeTestAtIndex(index)

    if topLevel:
        self._tearDownPreviousClass(None, result)
        self._handleModuleTearDown(result)
        result._testRunEntered = False
    return result

因此,基本上,这段代码的作用是迭代套件所具有的每个测试并调用它:

for index, test in enumerate(self):
    ...
    if not debug:
        test(result)  # This is the line throwing the error
    ...

如您所见,该循环在套件本身上进行迭代,因此我必须在 somewhere 中定义一个__iter__方法。在类TestSuite中找不到它5分钟后,我意识到是拥有此类方法的父类。这是我在BaseTestSuite中找到的:

def __iter__(self):
    return iter(self._tests)

基本上,它只是返回测试的 iterator 。在那一刻,这样的代码行是我无法确定的一堵高墙。但是我没有放弃,回到了TestSuite.run的定义,并且奇迹般地,我发现了以下几行:

...
if self._cleanup:
    self._removeTestAtIndex(index)
...

这让我感到奇怪:“测试被删除了吗?让我调查一下” 。然后我就很开明,因为在_removeTestAtIndex里面发现了这一行:

self._tests[index] = None

故事的结尾。因此,在第一次运行所有测试后,它们只转换为None:套件内的测试列表最终为None s([None, None, ..., None]的列表)。

因此,您如何防止这种行为?只需关闭套件中的_cleanup标志即可。这应该起作用:

答案

import unittest

suite = unittest.TestLoader().discover("./tests")
suite._cleanup = False  # Pervent such cleanup

runner = unittest.TextTestRunner()

for build in [build1, build2]:
    get_the_build(build)
    runner.run(suite)

很抱歉,但是,除了向您展示如何解决问题外,我还想教您如何调试任何东西。

让我知道这是否真的对您有用。否则,请告诉我出了什么问题。

答案 1 :(得分:0)

感觉就像一个可怕的黑客,但每次在运行之前制作套件的深层副本为我解决了问题:

import unittest
import copy

suite = unittest.TestLoader().discover("./tests")
runner = unittest.TextTestRunner()

for build in [build1, build2]:
    get_the_build(build)
    runner.run(copy.deepcopy(suite))