Подтвердить что ты не робот

Может ли Python unittest тестировать параллельно, как нос?

Платформа тестирования Python NOSE предполагает параллельное выполнение нескольких тестов.

Цель этого состоит не в том, чтобы проверить параллельность в коде, а в том, чтобы тесты для кода, у которого "нет побочных эффектов, нет проблем с упорядочением и нет внешних зависимостей", выполнялись быстрее. Повышение производительности достигается за счет одновременного ожидания ввода-вывода, когда они обращаются к различным устройствам, лучшего использования нескольких процессоров/ядер и параллельного выполнения операторов time.sleep().

Я полагаю, что то же самое можно сделать с помощью инфраструктуры тестирования юнитов Python с помощью плагина Test Runner.

Кто-нибудь имел опыт работы с таким зверем, и могут ли они дать какие-либо рекомендации?

4b9b3361

Ответ 1

Python unittest встроенный testrunner не запускает тесты параллельно. Вероятно, это было бы не слишком сложно написать то, что было сделано. Я написал свой собственный, чтобы переформатировать выход и время каждого теста. Это заняло, может быть, 1/2 в день. Я думаю, вы можете поменять класс TestSuite, который используется с производным, который использует многопроцессор без особых проблем.

Ответ 2

Пакет 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())

Ответ 3

Пожалуйста, используйте pytest-xdist, если вы хотите параллельный запуск.

Плагин pytest-xdist расширяет py.test некоторыми уникальными режимами выполнения теста:

  • распараллеливание тестового прогона: если у вас несколько процессоров или хостов, вы можете использовать их для комбинированного тестового прогона. Это позволяет ускорить разработку или использовать специальные ресурсы удаленных машин.

[...]

Больше информации: блог Рохана Данхэма

Ответ 4

Если вам нужна только поддержка Python3, подумайте об использовании моего fastunit.

Я просто изменяю несколько кода unittest, делая тестовый сценарий выполненным как сопрограммы.

Это действительно спасло мое время.

Я только что закончил его на прошлой неделе и, возможно, недостаточно тестирую, если произошла какая-либо ошибка, сообщите мне, чтобы я мог сделать это лучше, спасибо!

Ответ 5

Другой вариант, который может быть проще, если у вас не так много тестовых примеров, и они не зависят от них, - это запустить каждый тестовый пример вручную в отдельном процессе.

Например, откройте пару сеансов tmux, а затем начните тестовый пример в каждом сеансе, используя что-то вроде:

python -m unittest -v MyTestModule.MyTestClass.test_n

Ответ 6

Если это то, что вы сделали первоначально

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)

Ответ 7

Вы можете переопределить 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, я просто построить свой индивидуальный TestSuite класс CustomTestSuite, добавьте все тестовые случаи, и, наконец, запустить его.