klen.github.io

in Blog

Ускорение python тестов. Конкурентный запуск.

Mixer -- удобная генерация данных для тестирования Ctrl→
←Ctrl Неделя с Flask

Содержание:


Прогон тестов на большом проекте отнимает продолжительное время. Простейший способ ускорить выполнение на многоядерной системе использовать конкуренцию.

Nose

Если вы используете nose для тестирования, то у вас уже есть возможность конкурентного запуска.

"You can parallelize a test run across a configurable number of worker processes. While this can speed up CPU-bound test runs, it is mainly useful for IO-bound tests that spend most of their time waiting for data to arrive from someplace else and can benefit from parallelization."

nose

Обычный запуск тестов выглядит так:

$ nosetests

Для использования конкуренции:

$ nosetests --processes=4

Unittest

Все хорошо, но как ускорить тесты если вы не используете nose? Задавшись этим вопросом я обнаружил модуль concurrencytest от Corey Goldberg. Он позволяет переопределить unittest.TestSuite для использования конкурентного запуска тестов.

Базовый пример использования:

from concurrencytest import ConcurrentTestSuite, fork_for_tests

# Здесь мы определяем unittest.TestSuite и unittest.TestRunner
suite = ...

# И обеспечиваем конкурентный запуск
concurrent_suite = ConcurrentTestSuite(suite, fork_for_tests())
runner.run(concurrent_suite)

Вот и все. Теперь тесты будут выполняться асинхронно.

Note

По умолчанию concurrencytest запускает форки по количеству ядер процессора вашей системы. Вы можете явно указать количество процессов при создании ConcurrentTestSuite задав аргумент для функции для fork_for_tests (fork_for_tests(8)).

Django

Приведу пример интеграции concurrencytest в Django проект. Нужно создать собственный TestRunner который переопределит создаваемую TestSuite. Пусть код проекта лежит в модуле main, создадим файл main/test_runner.py:

from concurrencytest import ConcurrentTestSuite, fork_for_tests
from django.test.simple import DjangoTestSuiteRunner


class DjangoConcurencyTestSuiteRunner(DjangoTestSuiteRunner):

    def build_suite(self, test_labels, extra_tests=None, **kwargs):
        suite = super(DjangoConcurencyTestSuiteRunner, self).build_suite(
            test_labels, extra_tests=extra_tests, **kwargs
        )

        concurrency_suite = ConcurrentTestSuite(suite, fork_for_tests())
        return concurrency_suite

Теперь осталось переопределить опцию TEST_RUNNER в настройках Django, указав путь импорта к нашему раннеру:

TEST_RUNNER = 'main.test_runner.DjangoConcurencyTestSuiteRunner'

Не забудьте добавить в ваше окружение зависимость от concurrencytest и обновить его.

Все, теперь запуск Django тестов будет автоматически использовать все присуствующие в системе ядра процессора. В моих проектах прирост скорости выполнения тестов составил от 35% до 60% процентов, на 4-х ядерном macbook pro.

Note

Кстати если вы используете tox для прогона тестов используя разные окружения, то для использования конкурентного запуска вам достаточно просто установить модуль detox и запускать tox-тесты с его помощью.

Mixer -- удобная генерация данных для тестирования Ctrl→
←Ctrl Неделя с Flask
alt