Fork me on GitHub
13/9/2009

Внешние инструменты тестирования для Django

Кому-то хватает стандартных инструментов тестирования 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. Интеграция заключается в

  1. По сайту можно ходить по относительным ссылкам, так что не нужно привязываться к хосту и порту для twill
  2. Возможность использовать reverse-resolving вместе урлов
  3. Возможность для аутентификации давать экземпляр 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".

Что я выбрал и почему

Когда я начинал делать обзор, я искал инструмент, который позволил бы мне:

  1. Использовать уже написанные тесты, которые на момент анализа запускались python manage.py test
  2. Писать некоторые тесты в nose-стиле (т.е. assert'ами, а не используя xUnit API). Это не потому, что мне не нравится xUnit API, а потому что кое-какие тесты проще писать и поддерживать именно в nose-стиле.
  3. Первые два пункта были критическими, а этот пункт опциональным: по возможности дать привязки к 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 подробнее я расскажу в другой раз ;)

Комментарии

Все статьи