2016-12-24 10 views
10

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

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

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

Похоже, что тесты фактически связаны.

Вопрос: Возможно ли автоматическое обнаружение всех связанных тестов в проекте?

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

ответ

4

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

С pytest, который является то, что я использую, можно реализовать сценарий, который первый запускает его с --collect-only, а затем использовать узел иду теста вернулся, чтобы начать индивидуальный 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/

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

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

+0

Hi Jāzeps, придуманный, увидев вас здесь! 'pytest-random-order' выглядит хорошо, я проверю его. – wim

+0

Ха-ха, эй @wim спасибо! – jbasko

2

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

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

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

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

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

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

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

0

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

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

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

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

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

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 

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

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

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

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

+0

Этот вопрос спорный в контексте вопроса. Класс Django 'TestCase' является [' TransactionTestCase'] (https://docs.djangoproject.com/en/1.10/topics/testing/tools/#django.test.TransactionTestCase), который обрезает таблицы между тестами. Срыв обычно не требуется, и реализация вашего предложения будет просто пустой тратой времени. – wim

+0

@wim - спасибо, что указали это. Я изменил свой ответ, чтобы больше сосредоточиться на более общих типах загрязнения (которому должно быть дано описание ОП их проблемы) и меньше на состоянии самого теста db (что в случае стандартного Django вы правы в заявив, что отрыв обычно не требуется, поскольку каждый тест действительно выполняется в транзакции, которая автоматически откатывается в конце теста). –

+0

Я думаю, что у вас недостаточно опыта работы с Django, чтобы ответить на этот вопрос. Вызов 'teardown' не будет работать, он называется' tearDown'. Также сброс '* args, ** kwargs', как правило, не требуется для срыва, но удаление их в' супер'-вызове, как вы это сделали, может ужасно ломать вещи в нескольких ситуациях наследования. И вам почти никогда не понадобится утверждать, что что-то сравнивается с «Истиной». – wim

1

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

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

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

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

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

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

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

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

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