Автоматическое определение тестовой муфты

У нас есть большая тестовая кодовая база с более чем 1500 тестами для приложения Python/Django. Большинство тестов используют factory-boy для генерации данных для моделей проекта.

В настоящее время мы используем nose test runner, но открываем для перехода на py.test.

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

Похоже, что тесты действительно связаны.

Вопрос: Можно ли автоматически определять все связанные тесты в проекте?

Мое текущее мышление заключается в том, чтобы запускать все тесты в разных случайных комбинациях или порядке и сообщать о сбоях, может ли nose или py.test помочь с этим?

Ответ 1

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

С помощью pytest, который я использую, вы можете реализовать script, который сначала запускает его с помощью --collect-only, а затем использует теги node, возвращаемые для запуска отдельного прогона pytest для каждого из их. Это займет много времени для ваших 1500 тестов, но он должен выполнять эту работу, пока вы полностью воссоздаете состояние вашей системы между каждым отдельным тестом.

Для приблизительного ответа вы можете попробовать выполнить тесты в случайном порядке и посмотреть, сколько начинается сбой. Недавно у меня был аналогичный вопрос, поэтому я попробовал два плагина pytest - pytest-randomly и pytest-random: https://pypi.python.org/pypi/pytest-randomly/ https://pypi.python.org/pypi/pytest-random/

Из двух, pytest-randomly выглядит более зрелым и даже поддерживает повторение определенного порядка, принимая параметр seed.

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

Я написал свой собственный плагин, который позволяет мне контролировать уровень, на котором тесты могут произвольно изменять порядок (модуль, пакет или глобальный). Он называется pytest-random-order: https://pypi.python.org/pypi/pytest-random-order/

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

Если тест X выходит из строя как часть более крупного набора тестов, а затем не запускается индивидуально, тогда этот тест X не выполняет достаточно хорошую работу по настройке среды для теста.

Ответ 2

Поскольку вы уже используете фреймворк nosetests, возможно, вы можете использовать nose-randomly (https://pypi.python.org/pypi/nose-randomly) для запуска тестовых примеров в случайном порядке.

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

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

В идеале невозможно идентифицировать тестовые зависимости и сбои, если вы не запустите все комбинации 1500 тестов, которые составляют 2 ^ 1500-1.

Таким образом, привычка запускать тесты со случайным включением всегда, когда вы их запускаете. В какой-то момент вы столкнетесь с ошибками и продолжаете работать, пока не поймаете как можно больше сбоев.

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

Надеюсь, что это поможет, и это то, что мы делаем на нашем рабочем месте, чтобы достичь той же ситуации, которую вы пытаетесь достичь.

Ответ 3

Я решил аналогичные проблемы в большом проекте Django, который также использовал носовой бегун и factory -boy. Я не могу сказать вам, как автоматически обнаруживать тестовую связку, как заданный вопрос, но я имею задним числом, чтобы рассказать о некоторых проблемах, вызывающих связь в моем случае:

Проверить весь импорт TestCase и убедиться, что они используют Django TestCase, а не unittest TestCase.. Если некоторые разработчики в команде используют PyCharm, который имеет удобный авто-импорт функция, это может быть очень легко случайно импортировать имя из неправильного места. Unittest TestCase будет успешно запускаться в большом наборе тестовых проектов Django, но вы не можете получить хорошие функции фиксации и отката, которые имеет тестовый пример Django.

Убедитесь, что любой класс тестов, который переопределяет setUp, tearDown, setUpClass, tearDownClass также делегирует super. Я знаю, это звучит очевидно, но это очень просто забыть!

Также возможно, чтобы изменчивое состояние прокралось из-за factory мальчика. Осторожно с использованием последовательностей factory, которые выглядят примерно так:

name = factory.Sequence(lambda n: 'alecxe-{0}'.format(n))

Даже если db чист, последовательность может не начинаться с 0, если другие тесты выполняются заранее. Это может вас укусить, если вы сделали утверждения с неправильными предположениями о том, какие значения будут использовать модели Django при создании factory boy.

Аналогично, вы не можете делать предположения о первичных ключах. Предположим, что модель django Potato отключена от автоматического поля, и в начале теста нет строк Potato, а factory мальчик создает картофель, т.е. Вы использовали PotatoFactory() в setUp. Вам не гарантировано, что первичный ключ будет 1, что удивительно. Вы должны содержать ссылку на экземпляр, возвращаемый factory, и делать утверждения против этого фактического экземпляра.

Будьте очень осторожны и с RelatedFactory и SubFactory. factory У мальчика есть привычка выбирать любой старый экземпляр для удовлетворения отношения, если он уже существует, висящий в db. Это означает, что вы получаете как связанный объект, иногда не повторяется - если другие объекты создаются в setUpClass или светильниках, связанный объект, выбранный (или созданный) с помощью factory, может быть непредсказуемым, потому что порядок тестов произволен,

Ситуации, в которых модели Django имеют @receiver декораторов с post_save или pre_save, крючки очень сложно обрабатывать с помощью factory boy. Для лучшего контроля над связанными объектами, включая случаи, когда просто захват какого-либо старого экземпляра может быть неправильным, вам иногда приходится обрабатывать данные самостоятельно, переопределяя метод класса _generate на factory и/или используя собственные перехватчики, используя @factory.post_generation декоратор.

Ответ 4

Это происходит, когда тест не разрушает окружающую среду.

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

Вместо того, чтобы сосредоточиться на связи между тестами (которые в приведенном выше случае будут несколько спорными, поскольку это может зависеть от того, в каком порядке они выполняются), возможно, было бы лучше запустить процедуру, которая проверяет рутинную процедуру каждого теста.

Это может быть выполнено путем обертывания исходного класса Test и переопределения функции teardown, чтобы включить какой-то общий тест, что тестовый env был надлежащим образом reset для данного теста.

Что-то вроде:

class NewTestClass(OriginalTestClass):
   ...

    def tearDown(self, *args, **kwargs):
        super(NewTestClass, self).tearDown(*args, **kwargs)
        assert self.check_test_env_reset() is True, "IM A POLLUTER"

И затем в тестовых файлах заменить оператор импорта исходного тестового класса на новый:

# old import statement for OriginalTestClass
from new_test_class import NewTestclass as OriginalTestClass

Впоследствии запуск тестов должен привести к сбоям для тех, которые вызывают загрязнение.

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

В этой более поздней перспективе неудачные тесты - это хорошо написанные тесты и должны быть исправлены индивидуально.

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