В Django есть такая удобная вещь для написания тестов — это fixtures. Удобство состоит в том, что ваши тесты могут входить в уже заполненный данными проект. Например тестируем работу админчасти статистики, надо иметь готовый массив данных, с которым оперируем и проверяем результаты. Неудобство состоит в том, что эти фикстуры надо где взять, надо поддерживать актуальными, такими-же актуальными как и тесты. Вот как раз и про неудобную часть, а также паре подводных камней я бы и хотел вам рассказать.
Получаем фикстуру
-
python manage.py dumpdata > all_data.json
Рассказывать, что это означает я не буду, но то есть хорошая документация по фикстурам у самой Django .
Я «обплетал» тесты уже готового написанного проекта. В нагрузку с проектом идет дамп базы, которая, как это не удивительно, может быть не целостная. Самый часты бок — это когда записи по форенключу нет. Например у Вас есть профиль, но нет юзера или есть транзакция между не существующими счетами.
Самое обидное, что Джанго Вам не поможет решить эту проблему. И получите что-то типа
Error: Unable to serialize database:
Нагугил тикет в Django Code:
https://code.djangoproject.com/ticket/6773
К которому прилагается команда, которая показывает Вам «разбитые модели», т. е. модели не полные с неверными данными в ForeignKey .
Я ее немного приукрасил возможностью удалять их автоматом https://gist.github.com/1018947. Для реальных данных удаление автоматом — это не очень обдуманный шаг, но мне сейчас надо получить хоть какую-то фикстуру.
Подержание актуальности фикстуры
Для поддержки актуальности базы между всеми разработчиками используется django-south, мне кажется это уже давно стало стандартом Django разработки. Тот же механизм можно использовать для поддержки актуальности с фикстурами, поэтому я одну фикстуру полностью перегоняю в sqlite3 базу, которую как и фикстуру держу в репозитарии проекта и для доступа к которой использую отдельный сетингс.
Сеттингс файл для этого состоит из 3х строчек (settings_lights.py):
-
from settings import *
-
DATABASES['default']['ENGINE'] ='django.db.backends.sqlite3'
-
DATABASES['default']['NAME'] = 'lights.db'
Как известно, в любую команду можно передать не стандартное имя сетингс модуля.
Например, для того, чтоб запустить его и добавить новых данных:
-
python manage.py runserver 0:8001 –settings=settings_lights
-
python manage.py dumpdata –setting=settings_lights > all_data.json
А поддерживать актуальность фикстуры можно через миграции, которые вы создаете после изменения структуры базы
-
python manage.py migrate –settings=settings_lights
-
python manage.py dumpdata –setting=settings_lights > all_data.json
Тестирование
Для тестирования я использую тот-же settings_lights.py для того, чтобы использовать sqlite3 в тестах, при этом для тестов вся база будет держаться в памяти, что существенно ускорит процесс написания тестов и тестирования их.
-
python manage.py test –settings=settings_lights
Но я думаю как финальную проверку, после того, как вы закончили с разработкой ( доработкой ) тестов можно использовать и реальный Engine.
А собственно сам текст тестов может выглядить так:
-
from django.test import TestCase
-
from django.test.client import Client
-
from django.contrib.auth.models import User
-
-
class SimpleTest(TestCase):
-
fixtures = ['all_data.json']
-
def setUp(self):
-
self.client = Client()
-
-
def test_details(self):
-
print User.objects.all()
Этот пример ничего не тестирует, а просто показывает Вам, что данные на момент запуска тестов в базе уже есть. Фикстуры можно хранить как в папке fixtures любой апы, не только тестируемой. А еще в сетингсах можно прописать:
-
FIXTURE_DIRS = (
-
'/path/to/myapp/fixtures/',
-
)
Проблема с сигналами
Про сигналы в Django вы можете почитать в документции.
Фикстура — это по сути сериализация ОРМ объектов, т. е. объект будет сохранен как json, как просто текст. А значит загрузка из фикстуры — это поочередное добавление всех объектов, а добавление объектов связано с вызовом сигналов, которые в свою очередь могу сами создавать объекты моделей или изменять существующие.
Например. У Вас есть 2 модели счета и транзакции. При добавлении транзакции — дергается сигнал, по которому изменяются балансы счетов участников этой транзакции. При подготовке фикстуры вы создали одну транзакцию между двумя счетами на сумму 100 рублей, т. е. после ее проведения на одном счету прибавится 100 рублей, а на другой вычтится. Вы сохраните полученные данные в файл фикстуры, в которой будут готовые записи со счетами и транзакциями. Во время тестирования этот файл будет загружаться и вначале загрузятся модели счетов – на одном 100, на другом -100. После загрузятся транзакции и дернут сигнал, который еще раз изменит балансы на счетах и мы во время тестирования увидим состояния на счетах 200 и -200.
Решение у джанги есть , но почему-то не документированное, и как по мне — очень не удачное.
В обработчик сигнала передается параметр raw который True во время загрузки фиксутры.
Так что, если вы не хотите, чтоб обработчик сигнала работал в момент загрузки фикстуры, то первые 3 строчки вашего обработчика могут выглядит так:
-
def trans_save(sender, instance, raw, **kwargs):
-
if raw:
-
return
Как по мне — это недокументированную возможность надо огромными буквами задокументировать в обоих разделах — тесты и сигналы, но я думаю будут решения и лучше этой проблемы.
У меня все. Я описал то, как с фикстурами работаю я, и очевидно, что они могут сэкономить очень много времени Вам при разработке тестов, а также могут помогать Вам делать более качественные и реальные тесты.
Хотелось бы в комментариях увидеть критику такого подхода, дополнения, подводные камни, с которым вы сталкиваетесь. Буду дополнять статью Вашими цитатами и идеями.
Спасибо, и удачных Вам выходных.