Python的单元测试可以并行测试吗,比如鼻子可以吗?

时间:2011-01-17 04:57:40

标签: python unit-testing

Python的NOSE测试框架具有running multiple tests in parallel的概念。

这样做的目的不是测试代码中的并发性,而是为没有副作用,没有排序问题,没有外部依赖性的代码进行测试,运行得更快。性能增益来自于访问不同设备时的并发I / O等待,更好地使用多CPU /内核以及并行运行time.sleep()语句。

我相信使用Python的单元测试框架可以通过插件Test Runner完成同样的事情。

有没有人有这种野兽的经验,他们可以提出任何建议吗?

7 个答案:

答案 0 :(得分:21)

Python unittest的内置testrunner不会并行运行测试。写一个这样做可能不会太难。我自己写的只是为了重新格式化每个测试的输出和时间。那可能每天花费1/2。我认为你可以换掉与使用多进程的派生类一起使用的TestSuite类,而不会有太多麻烦。

答案 1 :(得分:19)

testtools包是unittest的扩展,它支持并发运行测试。它可以与继承unittest.TestCase

的旧测试类一起使用

例如:

import unittest
import testtools

class MyTester(unittest.TestCase):
    # Tests...

suite = unittest.TestLoader().loadTestsFromTestCase(MyTester)
concurrent_suite = testtools.ConcurrentStreamTestSuite(lambda: ((case, None) for case in suite))
concurrent_suite.run(testtools.StreamResult())

答案 2 :(得分:6)

如果您想要并行运行,请使用pytest-xdist

  

pytest-xdist插件使用一些独特的测试执行模式扩展了py.test:

     
      
  • 测试运行并行化:如果您有多个CPU或主机,则可以将它们用于组合测试运行。这样可以加快开发速度或使用远程机器的特殊资源。
  •   
     

[...]

更多信息:Rohan Dunham's blog

答案 3 :(得分:3)

如果您没有那么多测试用例并且它们不依赖,那么另一个可能更容易的选择是在一个单独的过程中手动启动每个测试用例。

例如,打开几个tmux会话,然后在每个会话中使用类似以下的命令启动测试用例:

python -m unittest -v MyTestModule.MyTestClass.test_n

答案 4 :(得分:2)

如果您只需要Python3支持,请考虑使用我的fastunit

我只需更改一些unittest代码,将测试用例作为协同程序运行。

这真的节省了我的时间。

我上周刚刚完成它,可能测试不够,如果发生任何错误,请告诉我,以便我能做得更好,谢谢!

答案 5 :(得分:1)

如果这是你最初做的

runner = unittest.TextTestRunner()
runner.run(suite)

-----------------------------------------

将其替换为

from concurrencytest import ConcurrentTestSuite, fork_for_tests

concurrent_suite = ConcurrentTestSuite(suite, fork_for_tests(4))
runner.run(concurrent_suite)

答案 6 :(得分:-1)

您可以覆盖unittest.TestSuite并实现一些并发范例。然后,您可以像常规TestSuite一样使用自定义的unittest类。在以下示例中,我使用TestSuite实现了自定义的async类:

import unittest
import asyncio

class CustomTestSuite(unittest.TestSuite):
    def run(self, result, debug=False):
        """
        We override the 'run' routine to support the execution of unittest in parallel
        :param result:
        :param debug:
        :return:
        """
        topLevel = False
        if getattr(result, '_testRunEntered', False) is False:
            result._testRunEntered = topLevel = True
        asyncMethod = []
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        for index, test in enumerate(self):
            asyncMethod.append(self.startRunCase(index, test, result))
        if asyncMethod:
            loop.run_until_complete(asyncio.wait(asyncMethod))
        loop.close()
        if topLevel:
            self._tearDownPreviousClass(None, result)
            self._handleModuleTearDown(result)
            result._testRunEntered = False
        return result

    async def startRunCase(self, index, test, result):
        def _isnotsuite(test):
            "A crude way to tell apart testcases and suites with duck-typing"
            try:
                iter(test)
            except TypeError:
                return True
            return False

        loop = asyncio.get_event_loop()
        if result.shouldStop:
            return False

        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)):
                return True

        await loop.run_in_executor(None, test, result)

        if self._cleanup:
            self._removeTestAtIndex(index)

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')


    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())


    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)


if __name__ == '__main__':
    suite = CustomTestSuite()
    suite.addTest(TestStringMethods('test_upper'))
    suite.addTest(TestStringMethods('test_isupper'))
    suite.addTest(TestStringMethods('test_split'))
    unittest.TextTestRunner(verbosity=2).run(suite)

main中,我只构建了自定义的TestSuiteCustomTestSuite,添加了所有测试用例,最后运行它。