Кому-то хватает стандартных инструментов тестирования Django,
кому-то нет. Мне стандартного мало и я сделал обзор сторонних инструментов
тестирования в Django.
В обзор попали:
Самое забавное, что я опросил уважаемых мною знакомых “джангонавтов”, и оказалось, что народ в большинстве своём удовлетворяется стандартными django.test.TestCase и django.test.Client
.
Django sane testing
Sane testing закрепляет стратегию тестирования “нужна БД — django.test.TestCase, можно обойтись без БД — unittest.TestCase” и делает ее более явной. Sane testing предоставляет базовые классы для тестирования в стиле xUnit в следующих вариациях:
- если тестовая БД не требуется, то
UnitTestCase - если требуется тестовая БД, но каждый тест можно “обернуть” транзакцией и
после успешного выполнения теста эту транзакцию откатить —
DatabaseTestCase
(сюда же относятся и тесты, использующие django.test.Client) - если нужна тестовая БД и тесты используют транзакции (в этом случае, БД
сбрасывается для каждого теста) —
DestructiveDatabaseTestCase - если для тестов нужен http-сервер (например, для проверки http basic/digest
аутентификации) — то
HttpTestCase (он же является и деструктивным для БД) - для функциональных тестов при помощи Selenium RC —
SeleniumTestCase
Django test utils
Django-test-utils примечателен оригинальными идеям, однако реализация хромает. Первым, чем выделяются test utils — генератор функциональных тестов.
Вы запускаете manage.py testmaker, запускается обычный dev-http сервер, ходите по ссылкам, а testmaker записывает ваши действия. Потом вы
останавливаете dev-http сервер и вуаля — у вас есть сгенерированные тесты.
Идея хороша. Для twill есть инструменты генерирования тестов, но не нативные,
а использующие более общие генераторы веб-тестов. Для Selenium есть родные
инструменты подготовки тестов “натыкиванием”, но сам Selenium, IMHO, не очень
удобно запускать (по крайней мере, в Django-инфраструктуре). Но тесты, которые
создает testmaker, мне не особо понравились: и стиль кода выпадает (например,
там для отступов используется tab), и сами тесты (в содержательной части)
мне не особо понравились.
Дальше больше и test utils предоставляют еще один интересный инструмент —
краулер. Он считывает urlconf, потом ходит по объявленным ссылкам (с опциональной
возможностью записывать время отклика) и отчитывается, на какие урлы из urlconf
он ни разу не ходил. Бывает полезно ;)
На этом дело не заканчивается, и test utils еще дают небольшую интеграцию
Django и twill, взятую у djutils.test. Правда, взята бездарно, потому
что testmaker не умеет генерировать тесты для twill.
В целом, у меня создалось впечатление, что эти же идеи можно было реализовать
намного более удачно и получить шикарный инструмент. Ну а пока, это
разношерстный набор оригинальных интересных инструментов, обязательных
к просмотру, а вот к использованию… по крайней мере я не решился.
Django satprep
Django satprep минималистичен: это nose test runner (взятый из basie) и небольшой полезный модуль для nose+twill, делающий преднастройку WSGI intercept. В двух словах: идея WSGI intercept в том, что для функционального тестирования используется не полноценный http-клиент и http-сервер, а как конечная цель тестирования используется WSGI приложение.
djutils.test
Djutils — эта куча всякого Django-related кода. Я не стал толком рассматривать
все его фичи, а сконцентрировался на тестировании, так что рассматриваем дальше
только djutils.test. Выше этот пакет уже упоминался, как оригинальное место,
откуда заимствована интеграция Django и twill. Интеграция заключается в
- По сайту можно ходить по относительным ссылкам, так что не нужно
привязываться к хосту и порту для twill
- Возможность использовать reverse-resolving вместе урлов
- Возможность для аутентификации давать экземпляр
django.contrib.auth.User
Из оставшихся фич: nose test runner (своя реализация) и py.test runner.
В целом, мне djutils не приглянулся, с миру по нитке, собственный стиль кода,
который мне не особо понравился, отсутствие какой-либо документации… В общем,
не очень хорошее впечатление.
NoseDjango
В NoseDjango применен метод “от противного”. Большинство проектов используют
возможности Django для применения кастомных test runner’ов. NoseDjango
наоборот, использует расширяемость nose и реализован в виде nose-плагина.
Инсталляция NoseDjango добавляет в nosetests опцию --with-django и
настраивает тестовое окружения Django перед запуском тестов. В принципе,
работает как заявлено, но мне пока что больше нравится запускать джанговские
тесты при помощи python manage.py test, хотя может и этот плагин распробую…
tddspry
Подход tddspry напоминает подход Django sane testing — явно разделенные базовые классы тестов:
NoseTestCase — полная аналогия unittest.TestCase, не использует БДDatabaseTestCase — тесты, которым нужны БДHttpTestCase — twill-тесты, также есть кое-какие хелперы.
В общем и целом, создалось впечатление “почти django sane testing с twill
вместо selenium”.
Что я выбрал и почему
Когда я начинал делать обзор, я искал инструмент, который позволил бы мне:
- Использовать уже написанные тесты, которые на момент анализа запускались
python manage.py test - Писать некоторые тесты в nose-стиле (т.е. assert’ами, а не используя
xUnit API). Это не потому, что мне не нравится xUnit API, а потому что
кое-какие тесты проще писать и поддерживать именно в nose-стиле.
- Первые два пункта были критическими, а этот пункт опциональным: по возможности дать привязки к twill (меня больше интересовал вопрос
автоматического обхода набора twill-тестов, чем Django-интеграция
и улучшенный API для twill’а).
Так вот, ни один из просмотренных инструментов не подходил под требования
пункта 1. Т.е. стандартный джанговский test runner нормально запускал тесты,
я переключался на альтернативный test runner, и тот не видел половину тестов.
Немного проясню причины. Дело в том, что стандартный django.test.simple.run_tests обходит все установленные приложения и ищет тесты в оговоренных местах: в модуле моделей и в модуле/пакете <app>.tests. Все рассмотренные здесь инструменты используют поиск тестов средствами nose. Таким образом, если у вас есть проект и подключены приложения вне дерева кода этого проекта, то джанговский test runner их запускает, а nose test discovering их не находит.
Поэтому я взял наиболее простой django-satprep и адаптировал его
под мои требования. Пункт 2 выполнился автоматически, а в качестве бонуса
nose ловит тесты в тест-пакете, которые и не перечислены в __init__.py внутри <app>.tests. По правде сказать, адаптация не даёт мне чувства законченности и у нее есть неизящные решения, наследованные от satprep (например, опции для nose передаются в виде python manage.py test -- -vds), но она удовлетворяет текущим требованиям, а дальше будет видно, то ли переключаться на NoseDjango, то ли дальше “дотачивать” свой форк satprep.
А про twill подробнее я расскажу в другой раз ;)