- PVSM.RU - https://www.pvsm.ru -
В Островке есть два основных продукта: для пользователей (ostrovok.ru) и «админка» для отелей (экстранет), куда подписанные нами отели заносят данные. Это отдельные продукты, со своими командами и различным отношением к разработке через тестирование (TDD). Одинаковая платформа: django и postgres. В экстранете используют TDD и у них куча тестов. Поначалу тесты были и в ostrovok.ru, но ввиду ухода части адептов в экстранет и очень интенсивного развития их перестали поддерживать. В общем передо мной встала задача внедрить тестирование. Первые шаги сделаны и хочу поделиться этим опытом и решениями, которые были применены.
У нас есть отдел QA и Selenium [1] автотесты, но это отдельно.
С django и тестами [2] вообще дела обстоят довольно хорошо и конечно лучше с самого начала все покрывать тестами, наращивая функционал и делая рефакторинги.
В нашем случае уже существовал огромный функционал и очень много всесторонних зависимостей и интеграции с внешними API. И нужно, чтоб это все работало в тестовой среде. Про быстрый SQLite в памяти можно забыть, в проекте есть привязки к особенностям postgres, да и идентичность тестового окружения все таки важна, поэтому тесты тоже работают на postgres.

Существует много видов тестирования [3], которые различаются по разным аспектам.
По изоляции мне нравятся больше интеграционные тесты, по тестируемому объекту — функциональные.
У таких тестов очень большое покрытие кода, это и плюс и минус одновременно.
Минус:
Плюсы:
Мы разрабатываем веб и в идеале мне не хочется открывать браузер для ручного тестирования моего кода. Хочется записать в тест все действия в браузере и добавить ряд проверок (отправка письма, наличие лога или какого-то объекта в базе). Когда буду писать код, мне нужно провести все эти действия вручную один раз точно, но в большинстве случаев это будет несколько раз. Записать действия в тест и прогнать десять раз по несколько секунд это намного круче, чем вручную сделать десять проверок. В браузере кроме основной разметки еще подгружаются стили, картинки, javascript и все это обычно сваливается на наш локальный runserver, а он не самый шустрый и зачастую работает в одном потоке, т.к. настраивать для разработки связку uwsgi и nginx как-то не хочется… Ну и в добавок выгода в том, что написанный тест, который помог в разработке, остается и играет важную роль в регрессионном тестировании [4].
Кроме тестирования http запросов [5] есть и другие тесты, например, тестирование django команд, с ними все аналогично. Обычные юниттесты тоже полезны. Когда привыкаешь пускать и писать тесты, то и стиль разработки меняется, процесс скорее будет итеративным: простой тест — нужный код, усложняем тест — дописываем код. Например: можно сделать опечатку, быстро запустить тест и увидеть опечатку и что тест не прошел.
Да и конечно есть места, где ручное тестирование сложно или даже почти невозможно, в этом случае тесты — это необходимость. Например: проверка правильного перехвата исключений и обработки ошибок, тонкие места логики…
В идеале — сначала тест.
Расписывать все преимущества разработки через тестирование не цель данной статьи, оставим это другим, например, Кенту Беку [6].
В TDD очень важная операция — запуск тестов. Обычно это даже не все тесты, а какой-то набор: тесты из пакета, отдельного модуля или вообще отдельный тест. Поэтому запуск отдельных тест кейсов должен быть быстрым.
В django с этим проблема, в ней перед каждым запуском теста создается база, и если схема большая, то это может занять и 30 секунд, а выполнение конкретного теста — меньше секунды. Не хочу ждать пока создается база.
Решение: вынести создание базы в отдельный шаг (использовать базу из предыдущих прогонов).
В рамках наших условий кроме схемы базы нам еще понадобились начальные данные:
Кажется что тут нового:
В «безтестное» время мне пришлось поучаствовать в мега рефакторинге, который был связан с импортом отелей. В ходе этой задачи у нас появились тесты хорошо покрывающие импорты. Эти тесты жили своей жизнью, мы их поддерживали в актуальном состоянии, чтоб они не стали мертвым грузом как другие существующие тесты, большую часть которых удалили.
Еще раз повторюсь, отели — сущность сложная, и создавать все связные объекты, а потом поддерживать все это хозяйство, совсем не хотелось. Тем более есть рабочий, протестированный код импортов, задача которых как раз создавать отели, его и заюзали. Меньше кода — меньше ошибок.
Тесты мы гоняем с nose [8], в целом это очень хороший инструмент для запуска тестов с поддержкой плагинов.
В итоге у нас получились свой раннер и ряд плагинов [9], решающие несколько проблем:
Есть процесс создания базы в зависимости от параметров командной строки:
...
--with-reuse-db # включает реиспользование базы, можно включить в настройках
--create-db # при включенном первом флаге пересоздает базу
...
В этом подходе есть минус: нужно помнить, что если меняется схема базы, то нужно базу пересоздать. Это не критично, важнее быстрый запуск.
Процесс создания начальной базы у нас уже может занимать до минуты при импорте ГИСа и отелей. Причем мы сохраняем две начальные базы: с отелями и без, т.к. при тестировании импортов нам отели не нужны. В конкретных TestCase мы задаем нужный нам шаблон базы.
В стандартном django подходе из TransactionTestCase [10] делается flush (полная очистка базы), потом восстанавливается начальная. Этот подход не работает, т.к. у нас отдельный шаг по созданию базы и чистить ее не нужно. При опции autocommit для postgres [11], flush выполнялся на каждый тест и это плохо — он долгий.
Чтоб ускорить тесты (относительно flush) мы использовали уникальную базу, которая создавалась по шаблону, postgres такое умеет:
src = self.db_conf['TEST_NAME']
new = '{0}_{1}'.format(src, uuid.uuid4().hex)
psql(
'DROP DATABASE IF EXISTS "{0}";'
'CREATE DATABASE "{0}" WITH TEMPLATE "{1}";'
.format(new, src)
)
Прирост был относительно flush в несколько раз и это казалось уже неплохо. Плюс уникальной базы на тест в том, что вероятность каких-то коллизий в базе нулевая, а с транзакциями они возможны. В конце концов пришли к варианту: по умолчанию работа в транзакции, т.к. это быстрее, а если у каких-то тестов проблемы — то уникальная база.
Для ускорения тестовой базы можно еще поставить в postgresql.conf:
fsync = off # turns forced synchronization on or offПрирост тоже ощущается. Ну и SSD винчестеры тоже хорошо :).
Такие тесты проще включить в процесс сборки, они достаточно быстро проходят (3-4 минуты ~250 тестов) и не задерживают особо релиз, они рядом с кодом. За временем выполнения тестов нужно следить и принимать меры по ускорению, т.к. количество тестов будет только расти, а значит — и время их выполнения.
Дальше в плане ускорения нужно параллелить запуск тестов, nose даже умеет [12], но свой код нужно дорабатывать.
Кроме быстрого запуска тестов, нужно еще и их ненапряжное написание. Когда у нас куча всесторонних зависимостей, первые тесты, которые повторяют основные действия пользователя, даются тяжело. Много мест нужно замокать, с многими местами разобраться. Поэтому было выделено время, чтоб сделать помощники, упрощающие написание таких тестов, с минимумом кода.
Благодаря существенному ускорению запуска тестов теперь они участвуют в сборке пакета: релиз не выкатывается, если есть упавшие тесты. Это тоже очень важный момент, т.к. есть явная связь: работающие тесты — релиз, неработающие тесты — нет релиза (релизы у нас частые, бывают несколько раз в день). Selenium автотесты живут пока отдельной жизнью, но команда работает над включением их в процесс непрерывной интеграции [13].
Тесты нам уже помогают:
В принципе начало положено, решения приняты, что будет дальше — время покажет.
P.S. python и postgres отличные инструменты — используйте.
Автор: naspeh [14]
Автор: Ostrovok
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/10270
Ссылки в тексте:
[1] Selenium: http://ru.wikipedia.org/wiki/Selenium
[2] django и тестами: https://docs.djangoproject.com/en/dev/topics/testing/
[3] много видов тестирования: http://ru.wikipedia.org/wiki/%D0%A2%D0%B5<code></code>%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BE%D0%B1%D0%B5%D1%81%D0%BF%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F#.D0.A2.D0.B5.D1.81.D1.82.D0.B8.D1.80.D0.BE.D0.B2.D0.B0.D0.BD.D0.B8.D0.B5_.D0.BF.D1.80.D0.BE.D0.B3.D1.80.D0.B0.D0.BC.D0.BC.D0.BD.D0.BE.D0.B3.D0.BE_.D0.BE.D0.B1.D0.B5.D1.81.D0.BF.D0.B5.D1.87.D0.B5.D0.BD.D0.B8.D1.8F
[4] регрессионном тестировании: http://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%80%D0%B5%D1%81%D1%81%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B5_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5
[5] тестирования http запросов: https://docs.djangoproject.com/en/dev/topics/testing/?from=olddocs#module-django.test.client
[6] Кенту Беку: http://ru.wikipedia.org/wiki/%D0%91%D0%B5%D0%BA,_%D0%9A%D0%B5%D0%BD%D1%82
[7] в django есть фикстуры: https://docs.djangoproject.com/en/dev/howto/initial-data/
[8] nose: http://nose.readthedocs.org/en/latest/
[9] свой раннер и ряд плагинов: https://gist.github.com/021bdc9b480c7efc9761
[10] TransactionTestCase: https://docs.djangoproject.com/en/dev/topics/testing/#django.test.TransactionTestCase
[11] autocommit для postgres: http://thebuild.com/blog/2012/03/19/a-recipe-for-django-transactions-on-postgresql/
[12] nose даже умеет: http://nose.readthedocs.org/en/latest/plugins/multiprocess.html
[13] непрерывной интеграции: http://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%BF%D1%80%D0%B5%D1%80%D1%8B%D0%B2%D0%BD%D0%B0%D1%8F_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B0%D1%86%D0%B8%D1%8F
[14] naspeh: http://habrahabr.ru/users/naspeh/
Нажмите здесь для печати.