Что не стоит тестировать в django проекте
Перейти к содержимому

Что не стоит тестировать в django проекте

  • автор:

Tech blog by @dizballanze

Медленные тесты не только тратят время разработчиков на ожидание, но и усложняют следование лучших практик TDD (red-green testing). Когда тестовый набор выполняется несколько минут или дольше — это приводит к тому, что весь набор тестов запускают редко и баги, которые можно было бы исправить раньше и быстрее, откладываются.

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

Параллельные тесты

Самый простой способ ускорить выполнение тестов без внесения каких-либо изменений в код — запуск тестов параллельно. Для этого в Django нужно задать параметр —parallel при запуске тестов, также этот параметр принимает опционально количество процессов. Если количество процессов не задано — берется равное количеству ядер процессора, для большинства случаев это оптимально.

Последовательное выполнение тестов из примера на моей машине длится:

# python manage.py test . ---------------------------------------------------------------------- Ran 11 tests in 8.012s

Если запустить с параметром —parallel :

# python manage.py test --parallel . ---------------------------------------------------------------------- Ran 11 tests in 2.628s

Тесты выполнились больше чем в 3 раза быстрее.

Стоит отметить, что Django распределяет выполнение различных тест кейсов (подклассов unittest.TestCase ) между разными процессами. Следовательно, если у вас тест кейсов меньше, чем количество ядер у процессора, то Django уменьшить количество процессов до количества тест кейсов. В нашем примере только 3 тест кейса, что ограничивает параллелизм 3мя процессами. На реальных проектах у вас как правило будут сотни или даже тысячи тест кейсов и эта проблема не будет актуальной.

Также при запуске тестов параллельно, Django иногда не может собрать трейсбеки ошибок, в случае ошибки вам придется перезапустить весь набор тестов последовательно.

Использование слабого алгоритма хэширования паролей

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

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

import sys TESTING = 'test' in sys.argv if TESTING: PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.MD5PasswordHasher', ] 

Протестируем время выполнения тестов после этого изменения:

# python manage.py test --parallel . ---------------------------------------------------------------------- Ran 11 tests in 0.564s

Быстрее в 4.65x раз �� Я видел, как этот простой хак увеличивал скорость выполнения огромного набора теста на порядок.

Создаем данные, когда они нужны

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

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

Я внес соответствующие изменения в наш пример. Посмотрим как это отразится на времени выполнения тестов:

# python manage.py test --parallel . ---------------------------------------------------------------------- Ran 11 tests in 0.353s

Еще на 60% быстрее.

setUpTestData

Базовый тест кейс Django предоставляет возможность создавать тестовые данные на уровне тест кейса, а не каждого теста. Это позволяет значительно ускорить выполнение тестов. Для этого нужно вынести создание данных в метод класса setUpTestData .

Созданные в setUpTestData объекты не должы меняться в процессе тестирования иначе это может привести к не стабильным тестам, т.к. тесты не будут полностью изолированными.

Здесь я добавил изменения в пример. Посмотрим на время выполнения тестов:

# python manage.py test --parallel . ---------------------------------------------------------------------- Ran 11 tests in 0.349s

Существенного преимущества мы не получили, но давайте проверим, что будет, если добавить еще несколько тестов. Без setUpTestData я получил результат 0.563s , с setUpTestData — 0.348s . Т.е. при использовании setUpTestData при добавлении новых тестов время практически не растет (кроме времени выполнения самого теста), т.к. не нужно для каждого нового теста заново создавать данные.

Заключение

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

live long and prosper ��

Тестирование проектов Django

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

  1. тестирование веб-сайтов — это сложно и непонятно
  2. юнит-тесты в django
  3. тестовая БД и как с ней бороться
  4. smoke testing
  5. покрытие кода (code coverage)
Тестирование веб-сайтов

Самый главный подводный айсберг тестирования Django-проектов заключается в том, что недостаточно написать тесты для питонокода. Разваливается верстка, JavaScript живет своей жизнью, веб-сервер не выдержал нагрузки и превратился в тыкву — все эти вещи выявить при помощи тестирования несколько сложнее, чем отследить неверный результат функции.

Поэтому проверка работоспособности веб-сайта — это обычно сложное явление, состоящее из нескольких независимых наборов тестов, часть которых (проверка внешнего вида в различных браузерах, например) может предполагать участие оператора. При отсутствии отдела QA роль тестировщика нередко возлагают на конечного пользователя, который потом всячески ругается. Так делать неправильно. Полковник Очевидность пост сдал.

Начнем же мы с (относительно) простых и понятных юнит-тестов.

Юнит-тесты в Django

Юнит-тесты в Django живут в модуле django.utils.unittest и являют собой расширение стандартного модуля unittest из поставки python 2.7 (unittest2). Что добавлено:

Тестовый HTTP-клиент. Имитирует работу браузера, может отправлять get- и post-запросы, сохраняет cookies между вызовами.

>>> from django.test.client import Client >>> c = Client() >>> response = c.post('/login/', ) >>> response.status_code 200 

С тестовым клиентом связан ряд ограничений. Например, запросить можно только относительный путь, URL вида http:/​/localhost:8000/ не сработает (по понятным причинам).

Расширенный набор проверок. Помимо стандартного набора, класс django.test.TestCase содержит также django-специфичные методы assert* , например:

assertContains(response, text, . ) # проверяет, что в ответе сервера содержится указанный текст; assertTemplateUsed(response, template_name, . ) # проверяет, что при рендеринге страницы использовался указанный шаблон; assertRedirects(response, expected_url, . ) # проверяет, было ли перенаправление; 

Тестирование почты. Модуль django.core.mail сохраняет в переменной outbox список всех отправленных посредством send_mail() писем.

Условное исключение тестов. В случае, если выбранная СУБД не поддерживает (или, наоборот, поддерживает) транзакционность, можно исключить заведомо сломанные тесты при помощи декоратора @skipUnlessDBFeature(‘supports_transactions’) или @skipIfDBFeature(‘supports_transactions’) .

Тестирование запускается вот так:

$ ./manage.py test [список приложений] 

По умолчанию прогоняются все тесты для всех приложений, перечисленных в INSTALLED_APPS . Пускалка (на языке оригинала — test runner) найдет юнит- и доктесты в файлах models.py и tests.py внутри каждого приложения. Чтобы импортировать доктесты из других модулей, можно использовать следующую запись:

from utils import func_a, func_b __test__ =

Здесь func_* — функция (или другая сущность), docstring которой нас интересует.

Для наблюдателя процесс тестирования выглядит следующим образом:

$ ./manage.py test main Creating test database for alias 'default'. . Ran 10 tests in 0.790s OK Destroying test database for alias 'default'. 
Тестовая БД и как с ней бороться

Для запуска тестов Django всегда создает новую БД, чтобы исключить вероятность уничтожения данных в рабочем окружении. Если в settings.py не указано иное, тестовая БД предваряется словом test_. Применимо к MySQL, привилегии обычно задаются как-то так:

GRANT ALL PRIVILEGES ON `project`.* TO 'user'@'localhost'; GRANT ALL PRIVILEGES ON `test_project`.* TO 'user'@'localhost'; 

Создавать саму БД test_project при этом не нужно.

Хозяйке на заметку. Все работает быстрее, если добавить в конфиг MySQL строку

[mysqld] skip-sync-frm=OFF 

Умозрительно, что сразу после создания никаких полезных данных в БД нет. Чтобы не порождать тестовый набор данных внутри каждого теста в отдельности, можно сделать это один раз и сохранить в fixture:

$ ./manage.py dumpdata > app/fixtures/test_data.json 
class HelloTestCase(TestCase): fixtures = ['test_data.json', 'moar_data.json'] 

И еще. Старайтесь использовать для разработки и тестирования ту же СУБД, что и на production-сервере. Это сделает ваш сон на 28% * спокойнее.

* научно доказано, что 87.56% статистики берется с потолка.

Smoke testing

В среде радиолюбителей термин smoke test означает буквально следующее: подключаем к свежесобранной схеме питание и наблюдаем, в каком месте из нее пошел дым. Если дым не пошел, можно приступать к более наукообразной проверке правильности работы схемы.

Описанный подход практикуют также при тестировании приложений. Применимо к Django имеет определенный смысл описывать в tests.py точки входа из URLconf, например, так:

urlpatterns = patterns(None, url(r'^registration/$', registration, name='registration'), url(r'^login/$', . name='login'), url(r'^logout/$', logout_then_login, name='logout'), ) 

tests.py

from django import test from django.core.urlresolvers import reverse __test__ = >> c = test.Client() >>> c.get(reverse('registration')).status_code 200 >>> c.get(reverse('login')).status_code 200 >>> c.get(reverse('logout')).status_code 302 """> 

Безусловно, такая проверка не заменит функционального тестирования регистрации и логина. Полковник Очевидность пост принял.

Покрытие кода (code coverage)

Покрытие кода — это метрика, показывающая, какой объем исходного кода был протестирован относительно всего объема полезного исходного кода в приложении. Низкое покрытие кода указывает на отсутствие тестов.

Хозяйке на заметку-2. Высокое покрытие кода не говорит об отсутствии ошибок (ни в коде, ни в тестах), это вымысел.

Для измерения покрытия кода на питоне существует coverage.py. Гугл помнит много попыток подружить coverage.py и Django, есть даже тикет #4501 (ему четыре года).

И сразу ложка дегтя: с Django 1.3 (и dev-версией) ни одно готовое решение для code coverage, похоже, не работает (поправьте меня, если это не так). Что, впрочем, не помешает нам запустить coverage.py руками.

$ coverage run --source=main,users manage.py test main users $ coverage html # генерация отчета 

Перечислим только интересующие нас модули (ключ —source); если не указать, там будет в том числе django, mysqldb и половина стандартной поставки питона.

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

В следующем выпуске: статический анализ как превентивная мера, тестирование верстки и JS, нагрузочное тестирование.

Учимся тестировать Django

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

1. Обзор тестирования

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

2. Тестируем код на хрупкость

tl; dr (краткий алгоритм) обнаружить ошибку, написать тест, который падает из-за ошибки, исправить ошибку, больше не волноваться.

Если вы не знаете, с чего начать, или если вы ищете способ облегчить тестирование, вам отлично подойдет тестирование антихрупкости. Антихрупкость основана на идее, что некоторые вещи становятся сильнее при воздействии критических состояний. По мере роста вашего приложения здесь и там будут появляться ошибки, и вам будет поручено их исправить. Однако, прежде чем исправить ошибку, вы можете написать тест, который не пройден из-за ошибки. Например, если у вас есть форма, которая создает пользователя и не проверяет правильность имени пользователя, вы можете написать тест, который специально проверяет проверку для этого поля. Вы можете вызвать тест username_validation_should_fail_when_length_is_less_than_8. Забавное имя для теста(и, возможно, вы могли бы его сократить), но важно писать очень конкретные тесты и давать осязаемые названия. Затем вы можете проверить валидацию, передав значения и в итоге должны потерпеть неудачу. Перед исправлением ошибки вы можете запустить тест и снова вызвать его неудачу. Затем, после исправления ошибки, вы можете быть уверены, что она снова не выйдет из строя.

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

3. Тестирование в Джанго

Более поздние версии Django поставляются с тестовым окружением. На самом деле это просто оболочка для встроенного в Python фреймворка для тестирования модулей. Вы захотите поместить свои тесты в папку приложения, к которой они принадлежат, и вызовете файл tests.py. Начиная с Django 1.6, Django будет искать тесты в любом файле, который начинается с ключевого слова test. Таким образом, вы можете лучше организовать свои тесты, а не писать несколько тысяч строк тестов в одном файле.

4. Пишем первый тест

Этот шаг предполагает, что вы уже настроили приложение Django. Откройте файл с именем tests.py в приложении по нашему выбору (в Django основное приложение называется проектом, и оно часто состоит из многих приложений).

В верхней части файла вы импортируете среду тестирования Django:

from django import test И любые тесты, которые вы пишете, расширят класс test.TestCase. Итак, в первом тесте мы предположим, что нам нужно проверить, возвращает ли домашняя страница статус ответа 200. Если вы не настроили файл urls или ваши представления, это нормально. Мы можем начать с написания теста. Когда вы пишете тесты, хорошо бы заставить тест упасть, чтобы вы знали, что он работает.

class URLTests(test.TestCase): def test_homepage(self): response = self.client.get('/') self.assertEqual(response.status_code, 200)

Django запустит набор тестов URLTests и выполнит любые методы, начинающиеся с test. Так что в этом случае test_homepage будет запущен автоматически. Таким образом, первая строка теста просто запускает HTTP-запрос GET для извлечения домашней страницы. Ответ хранится в переменной ответа, и мы используем метод TestCase assertEqual, чтобы убедиться, что код состояния равен 200.

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

5. Запуск тестов

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

./manage.py test Для запуска только тестов в определенном приложении, которое вы можете запустить:

./manage.py test app_name Как упоминалось ранее, эти команды найдут все применимые тесты и выполнят их. Тестовая база данных будет создана до и уничтожена после, и, как ожидается, вы будете уведомлены, если какие-либо тесты не пройдут.

Капнем глубже

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

Темы расширенного тестирования ¶

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

API RequestFactory немного более ограничен, чем у тестового клиента:

  • Это дает доступ к методам HTTP только get() , post() , put() , delete() , head() , options() и trace() .
  • Все эти методы принимают одни и те же параметры, за исключением из follow . Поскольку это только фабрика, производящая запросы, вы несете ответственность за ответ.
  • Он не поддерживает промежуточное ПО. Атрибуты сеанса и аутентификации должны быть предоставлены самим тестом, если необходимо, для правильной работы представления.

Пример ¶

Следующий пример представляет собой модульный тест с использованием фабрики запросов:

from django.contrib.auth.models import AnonymousUser, User from django.test import RequestFactory, TestCase from .views import MyView, my_view class SimpleTest(TestCase): def setUp(self): # Every test needs access to the request factory. self.factory = RequestFactory() self.user = User.objects.create_user( username='jacob', email='[email protected]…', password='top_secret') def test_details(self): # Create an instance of a GET request. request = self.factory.get('/customer/details') # Recall that middleware are not supported. You can simulate a # logged-in user by setting request.user manually. request.user = self.user # Or you can simulate an anonymous user by setting request.user to # an AnonymousUser instance. request.user = AnonymousUser() # Test my_view() as if it were deployed at /customer/details response = my_view(request) # Use this syntax for class-based views. response = MyView.as_view()(request) self.assertEqual(response.status_code, 200) 

AsyncRequestFactory ¶

RequestFactory создает запросы, подобные WSGI. Если вы хотите создавать запросы, подобные ASGI, в том числе иметь правильный ASGI scope , вы можете вместо этого использовать django.test.AsyncRequestFactory .

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

Тестирование представлений на основе классов ¶

Чтобы протестировать представления на основе классов вне цикла запроса / ответа, вы должны убедиться, что они настроены правильно, вызывая их setup() после их создания.

Например, начиная со следующего вида класса:

from django.views.generic import TemplateView class HomeView(TemplateView): template_name = 'myapp/home.html' def get_context_data(self, **kwargs): kwargs['environment'] = 'Production' return super().get_context_data(**kwargs) 

Вы можете напрямую протестировать метод get_context_data() , сначала создав экземпляр представления, затем передав запрос, setup() прежде чем приступить к тестированию кода:

from django.test import RequestFactory, TestCase from .views import HomeView class HomePageTest(TestCase): def test_environment_set_in_context(self): request = RequestFactory().get('/') view = HomeView() view.setup(request) context = view.get_context_data() self.assertIn('environment', context) 

Тесты с несколькими именами хостов ¶

Настройка ALLOWED_HOSTS проверяется во время выполнения тестов. Это позволяет тестирующему клиенту различать внутренние и внешние URL-адреса.

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

Первый вариант для этого — добавить хосты в файл настроек. Например, набор тестов docs.djangoproject.com содержит следующее:

from django.test import TestCase class SearchFormTestCase(TestCase): def test_empty_get(self): response = self.client.get('/en/dev/search/', HTTP_HOST='docs.djangoproject.dev:8000') self.assertEqual(response.status_code, 200) 

а файл настроек содержит список областей, поддерживаемых проектом:

ALLOWED_HOSTS = [ 'www.djangoproject.dev', 'docs.djangoproject.dev', . ] 

Другой вариант — добавить необходимые хосты ALLOWED_HOSTS с помощью override_settings() или modify_settings() . Этот вариант может быть предпочтительным для автономных приложений, которые не могут предоставить свой собственный файл настроек, или для проектов, список доменов которых не фиксирован (например, поддомены в общей службе). Например, можно было бы написать такой тест для домена http://otherserver/ :

from django.test import TestCase, override_settings class MultiDomainTestCase(TestCase): @override_settings(ALLOWED_HOSTS=['otherserver']) def test_other_domain(self): response = self.client.get('http://otherserver/foo/bar/') 

Отключение проверки ALLOWED_HOSTS ( ) во время выполнения тестов предотвращает создание тестовым клиентом информативного сообщения об ошибке, если для внешнего URL-адреса вызывается перенаправление. ALLOWED_HOSTS = [‘*’]

Тесты с несколькими базами данных ¶

Тестирование конфигурации первичной / реплики ¶

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

Чтобы обойти эту проблему, Django позволяет вам определить базу данных как тестовое зеркало . Рассмотрим этот (упрощенный) пример конфигурации базы данных:

DATABASES =  'default':  'ENGINE': 'django.db.backends.mysql', 'NAME': 'myproject', 'HOST': 'dbprimary', # . plus some other settings >, 'replica':  'ENGINE': 'django.db.backends.mysql', 'NAME': 'myproject', 'HOST': 'dbreplica', 'TEST':  'MIRROR': 'default', >, # . plus some other settings > > 

В этой конфигурации есть два сервера баз данных:, dbprimary определенные псевдонимом базы данных , default и dbreplica определенные псевдонимом replica . Как и следовало ожидать, dbreplica администратор базы данных настроил его как реплику для чтения dbprimary . Обычно любые надписи default также появляются replica .

Если бы Django создал две независимые тестовые базы данных, он сломал бы любые тесты, ожидающие репликации. Однако база данных replica была настроена как тестовое зеркало (с помощью настройки тестирования MIRROR ), что указывает на то, что при тестировании replica ее следует рассматривать как зеркало default .

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

Порядок создания тестовой базы данных ¶

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

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

DATABASES =  'default':  # . db settings 'TEST':  'DEPENDENCIES': ['diamonds'], >, >, 'diamonds':  # . db settings 'TEST':  'DEPENDENCIES': [], >, >, 'clubs':  # . db settings 'TEST':  'DEPENDENCIES': ['diamonds'], >, >, 'spades':  # . db settings 'TEST':  'DEPENDENCIES': ['diamonds', 'hearts'], >, >, 'hearts':  # . db settings 'TEST':  'DEPENDENCIES': ['diamonds', 'clubs'], >, > > 

В этой конфигурации база данных diamonds будет создана первой, потому что это единственный псевдоним базы данных без зависимостей. Затем будут созданы псевдонимы default и clubs (хотя порядок их создания не определен), затем hearts и наконец spades .

Если определение DEPENDENCIES имеет циклические зависимости, ImproperlyConfigured будет сгенерировано исключение .

Расширенные возможности TransactionTestCase ¶

TransactionTestCase. available_apps ¶

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

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

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

Установив available_apps список приложений, Django ведет себя так, как будто доступны только шаблоны для этих приложений. Поведение TransactionTestCase изменяется следующим образом:

  • post_migrate выдается перед каждым тестом для создания типов контента и разрешений для каждой модели доступных приложений в случае их отсутствия.
  • После каждого теста Django очищает только таблицы, соответствующие моделям доступных приложений. Однако на уровне базы данных усечения могут каскадироваться в связанные модели в недоступных приложениях. Кроме того, post_migrate не выдается; он будет выдан при следующем тесте TransactionTestCase после выбора правильного набора приложений.

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

Поскольку post_migrate не генерируется после перезагрузки базы данных, ее состояние после одного TransactionTestCase не такое, как после первого TestCase : строки, созданные получателями post_migrate , отсутствуют. Но знание порядка, в котором выполняются тесты, не является проблемой, поскольку либо все тесты TransactionTestCase в наборе тестов объявляют, available_apps либо ни одного.

available_apps требуется в собственном наборе тестов Django.

Определив класс , вы убедитесь, что последовательности всегда сбрасываются до запуска теста: reset_sequences = True TransactionTestCase

class TestsThatDependsOnPrimaryKeySequences(TransactionTestCase): reset_sequences = True def test_animal_pk(self): lion = Animal.objects.create(name="lion", sound="roar") # lion.pk is guaranteed to always be 1 self.assertEqual(lion.pk, 1) 

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

Использование тестирования замедляет работу, поскольку сброс первичных ключей — относительно дорогостоящая операция с базой данных. reset_sequences = True

Принудительное последовательное выполнение тестовых классов ¶

Если у вас есть тестовые классы, которые не могут работать параллельно (например, если они используют общий ресурс), вы можете использовать django.test.testcases.SerializeMixin их для последовательного запуска. Этот класс миксина использует файл блокировки lockfile файловой системы.

Например, вы можете использовать, __file__ чтобы определить, что все тестовые классы в одном файле, унаследованном от, SerializeMixin будут выполняться один за другим

import os from django.test import TestCase from django.test.testcases import SerializeMixin class ImageTestCaseMixin(SerializeMixin): lockfile = __file__ def setUp(self): self.filename = os.path.join(temp_storage_dir, 'my_file.png') self.file = create_file(self.filename) class RemoveImageTests(ImageTestCaseMixin, TestCase): def test_remove_image(self): os.remove(self.filename) self.assertFalse(os.path.exists(self.filename)) class ResizeImageTests(ImageTestCaseMixin, TestCase): def test_resize_image(self): resize_image(self.file, (48, 48)) self.assertEqual(get_image_size(self.file), (48, 48)) 

Использование средства запуска тестов Django для тестирования приложений многократного использования ¶

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

Распространенной практикой является размещение каталога тестов рядом с кодом приложения со следующей структурой:

runtests.py polls/ __init__.py models.py . tests/ __init__.py models.py test_settings.py tests.py 

Давайте посмотрим, что находится в некоторых из этих файлов:

runtests.py ¶

#!/usr/bin/env python import os import sys import django from django.conf import settings from django.test.utils import get_runner if __name__ == "__main__": os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_settings' django.setup() TestRunner = get_runner(settings) test_runner = TestRunner() failures = test_runner.run_tests(["tests"]) sys.exit(bool(failures)) 

Это сценарий, вызываемый для запуска набора тестов. Он настраивает среду Django, создает тестовую базу данных и запускает тесты.

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

tests / test_settings.py ¶

SECRET_KEY = 'fake-key' INSTALLED_APPS = [ "tests", ] 

Этот файл содержит необходимые настройки Django для запуска тестов вашего приложения.

Опять же, это минимальный пример; Для работы тестов могут потребоваться дополнительные настройки.

Поскольку пакет тестов включен INSTALLED_APPS при запуске тестов, можно определять модели только для тестов в файле models.py .

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

Ясно, что это unittest не единственная инфраструктура тестирования Python. Хотя Django не предоставляет явной поддержки для других систем тестирования, он предоставляет способ вызывать тесты, созданные для другой системы, как если бы они были обычными тестами Django.

При запуске Django проверяет настройку, чтобы определить, что делать. По умолчанию содержит . Этот класс определяет поведение тестирования Django по умолчанию. Это поведение включает: ./manage.py test TEST_RUNNER TEST_RUNNER ‘django.test.runner.DiscoverRunner’

  1. Общая подготовка перед экзаменами.
  2. Найдите тесты в любом файле ниже текущего каталога, имя которого соответствует шаблону test*.py .
  3. Создание тестовых баз данных.
  4. Запуск migrate для установки моделей и исходных данных в тестовые базы данных.
  5. Проведение системных проверок .
  6. Запуск обнаруженных тестов.
  7. Удаление тестовых баз.
  8. Общая доработка после тестов.

Если вы определите свой собственный класс запуска тестов и укажете этот класс в TEST_RUNNER , Django будет использовать вашу программу запуска тестов при каждом вызове . Таким образом, можно использовать любую тестовую систему, которую можно запустить из кода Python, или изменить процесс выполнения тестов Django, чтобы адаптировать тесты к любым конкретным потребностям. ./manage.py test

Определение исполнителя тестов ¶

Средство выполнения тестов — это класс, определяющий метод run_tests() . Django предоставляет класс, который DiscoverRunner определяет поведение тестирования Django по умолчанию. Этот класс определяет точку входа, run_tests() а также набор других методов, используемых run_tests() для подготовки, запуска и закрытия набора тестов.

class DiscoverRunner ( pattern = ‘test * .py’ , top_level = None , verbosity = 1 , interactive = True , failfast = False , keepdb = False , reverse = False , debug_mode = False , debug_sql = False , test_name_patterns = None , pdb = False , buffer = False , ** kwargs ) ¶

DiscoverRunner ищет тесты в любом файле, соответствующем шаблону pattern .

top_level может использоваться для указания каталога, содержащего модули Python верхнего уровня. Django обычно может обнаружить это автоматически, поэтому эта опция не является абсолютно необходимой. Когда он установлен, он обычно должен соответствовать каталогу, содержащему файл manage.py .

verbosity определяет количество уведомлений и отладочной информации, которые будут отображаться на консоли; 0 ничего не отображает, 1 соответствует нормальному отображению и 2 подробному отображению.

Если interactive есть True , то набор тестов имеет право задавать вопросы пользователю во время выполнения. Например, она может запросить разрешение на удаление существующей тестовой базы данных. Если interactive есть False , набор тестов должен быть в состоянии работать без какого — либо ручного вмешательства пользователя.

Если failfast равно True , набор тестов останавливается сразу после первого сбоя теста.

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

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

debug_mode указывает значение, которое должна принять настройка DEBUG перед запуском тестов.

Если debug_sql применимо True , тесты не пройдены, отображают SQL-запросы, записанные в django.db.backends, в дополнение к стеку вызовов. Если verbosity равно 2 , отображаются запросы для всех тестов.

test_name_patterns может использоваться для определения набора шаблонов для фильтрации методов и классов тестирования по имени.

Если pdb есть True , отладчик ( pdb или ipdb ) будет запускаться для каждой ошибки или сбоя теста.

Если buffer есть True , результаты прохождения тестов будут отброшены.

Время от времени Django может расширять возможности средства запуска тестов, добавляя новые параметры. Объявление **kwargs допускает это расширение. Если вы унаследовали от DiscoverRunner собственного средства выполнения тестов или написали его, убедитесь, что он принимает **kwargs .

Ваш тестовый исполнитель также может установить дополнительные параметры командной строки. Создайте или переопределите метод класса и добавьте настраиваемые параметры, вызвав этот метод, чтобы команда могла использовать эти параметры. add_arguments(cls, parser) parser.add_argument() test

Новое в Django 3.0:

Параметр pdb добавлен.

Новое в Django 3.1:

buffer Аргумент был добавлен.

Атрибуты ¶

DiscoverRunner. test_suite ¶

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

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

Это класс, который загружает тесты из классов TestCase , модулей или другими способами и объединяет их в наборы тестов, которые может запускать средство запуска. По умолчанию unittest.defaultTestLoader . Вы можете переопределить этот атрибут, если ваши тесты нужно загружать необычным образом.

Методы ¶

DiscoverRunner. run_tests ( test_labels , extra_tests = None , ** kwargs ) ¶

Запускает набор тестов.

test_labels позволяет указать тесты для запуска и принимает несколько форматов (см. DiscoverRunner.build_suite() список поддерживаемых форматов).

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

Этот метод должен возвращать количество неудачных тестов.

classmethod DiscoverRunner. add_arguments ( парсер ) ¶

Переопределите этот метод класса, чтобы добавить настраиваемые параметры, принимаемые командой администрирования test . См. argparse.ArgumentParser.add_argument() Дополнительные сведения о добавлении параметров командной строки.

DiscoverRunner. setup_test_environment ( ** kwargs ) ¶

setup_test_environment() Настраивает тестовую среду, вызывая и задавая DEBUG значение self.debug_mode (по False умолчанию).

DiscoverRunner. build_suite ( test_labels , extra_tests = None , ** kwargs ) ¶

Создает набор тестов, соответствующий заданным именам тестов.

test_labels это список строк, описывающих тесты, которые нужно запустить. Название теста может иметь четыре формы:

  • chemin.vers.module_test.TestCase.methode_test — Запускает единственный тестовый метод в тестовом примере.
  • chemin.vers.module_test.TestCase — Запускает все методы тестирования тестового примера.
  • chemin.vers.module — Находит и запускает все тесты для данного пакета или модуля Python.
  • chemin/vers/répertoire — Находит и запускает все тесты в поддереве данного каталога.

Если test_labels содержит значение None , средство запуска тестов ищет тесты во всех файлах ниже текущего каталога, имя которых соответствует его шаблону pattern (см. Выше).

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

Возвращает экземпляр, TestSuite готовый к выполнению.

DiscoverRunner. setup_databases ( ** kwargs ) ¶

Создает тестовые базы данных путем вызова setup_databases() .

DiscoverRunner. run_checks ( базы данных ) ¶

Запускает системные проверки по тесту databases .

Новое в Django 3.1:

databases Параметр был добавлен.

DiscoverRunner. run_suite ( продолжение , ** kwargs ) ¶

Запускает набор тестов.

Возвращает результат, полученный при запуске набора тестов.

Возвращает именованные параметры для создания экземпляра DiscoverRunner.test_runner .

DiscoverRunner. teardown_databases ( old_config , ** kwargs ) ¶

Удаляет тестовые базы данных, восстанавливая состояние до тестирования путем вызова teardown_databases() .

DiscoverRunner. teardown_test_environment ( ** kwargs ) ¶

Восстанавливает среду до состояния до тестирования.

DiscoverRunner. suite_result ( сюита , результат , ** kwargs ) ¶

Вычисляет и возвращает код возврата на основе набора тестов и результатов этих тестов.

Тестовые утилиты ¶

django.test.utils ¶

Для создания собственного средства выполнения тестов Django предоставляет в модуле ряд служебных методов django.test.utils .

setup_test_environment ( отладка = Нет ) ¶

Выполняет общую настройку перед тестированием, такую ​​как установка адаптаций системы рендеринга шаблонов и настройка фиктивного почтового ящика.

Если debug нет None , то настройка DEBUG обновляется с этим значением.

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

setup_databases ( многословие , интерактивность , keepdb = False , debug_sql = False , parallel = 0 , aliases = None , ** kwargs ) ¶

Создает тестовые базы данных.

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

Параметр aliases определяет, для каких псевдонимов DATABASES тестовых баз данных нужно подготовить. Если отсутствует, по умолчанию он содержит все псевдонимы DATABASES .

teardown_databases ( old_config , parallel = 0 , keepdb = False ) ¶

Удаляет тестовые базы данных, восстанавливая дотестовую ситуацию.

old_config — это структура данных, определяющая, какие изменения конфигурации базы данных следует откатить. Это возвращаемое значение метода setup_databases() .

django.db.connection.creation ¶

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

create_test_db ( многословность = 1 , autoclobber = False , serialize = True , keepdb = False ) ¶

Создайте новую тестовую базу данных и запустите migrate ее.

verbosity имеет тот же эффект, что и в run_tests() .

autoclobber описывает поведение, принятое, если база данных с тем же именем, что и тестовая база данных, уже существует:

  • Если autoclobber применимо False , пользователь должен подтвердить удаление существующей базы данных. sys.exit вызывается, если пользователь отказывается.
  • Если autoclobber применимо True , база данных удаляется без запроса пользователя.

serialize определяет, сериализует ли Django базу данных в строку JSON в памяти перед запуском тестов (используется для восстановления состояния базы данных между тестами, если транзакций нет). Вы можете установить его, чтобы False значительно ускорить время сборки, если ни один из ваших тестовых классов не имеет serialized_rollback = True .

Если вы используете средство запуска тестов по умолчанию, вы можете управлять этим с помощью ключа SERIALIZE словаря TEST .

keepdb определяет, следует ли в тестовом сеансе использовать существующую базу данных или создать новую. Если True используется возможная существующая база данных, в противном случае создается новая база данных. Да False , в любом случае будет создана новая база данных с запросом пользователя, следует ли ему удалить существующую базу данных, если таковая имеется.

Возвращает имя созданной тестовой базы данных.

create_test_db() имеет побочный эффект: значение NAME in DATABASES обновляется, чтобы соответствовать имени тестовой базы данных.

destroy_test_db ( Old_database_name , подробность = 1 , keepdb = False ) ¶

Удаляет базу данных, имя которой совпадает со значением NAME In, DATABASES и обновляет NAME значение в old_database_name .

Параметр verbosity имеет тот же эффект, что и для DiscoverRunner .

Если параметр keepdb установлен True , соединение с базой данных будет закрыто, но база данных не будет уничтожена.

Интеграция с coverage.py ¶

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

Django можно легко интегрировать с покрытием .py , инструментом для измерения покрытия кода программ Python. Сначала установите extension.py . Затем запустите следующую команду из каталога вашего проекта, содержащего manage.py :

coverage run --source='.' manage.py test myapp 

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

coverage report 

Обратите внимание, что код Django был выполнен во время выполнения тестов, но он не отображается в отчете из-за параметра, source указанного в предыдущей команде.

Дополнительные параметры, такие как аннотированные HTML-списки с подробным описанием отсутствующих строк, см. В документации по охвату .

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *