Pull to refresh

Comments 20

Суховато, конечно, но по делу, спасибо! Мы одну общую базу используем, больно тестов много, на каждый базу не наразворачиваешься. Вам повезло, что выбрав конкретные таблицы так получилось по производительности прыгнуть. Ну, в смыле, хорошо, что предметная область это позволяет. :)

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

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

Кстати, а зачем свой генератор данных

Потому что используется своя велосипедная ORM. А так, была рассмотрена похожая система для EntityFramework. Оттуда и черпались лучшие идеи для своего генератора, с некоторыми улучшениями и доработки. Генератор для EF, например, умел генерировать сущности только для одной конкретной таблицы, поэтому приходилось поочередно генерировать данные и потом их связывать. В разработанной системе, все с помощью правил, включая связь по вторичным ключам.

Мои пять копеек:


  • для некоторых тестов допустимо использовать встраиваемые СУБД (в случае Java, к примеру, H2), но это требует поддержки нескольких СУБД в проекте и плохо увязывается с MySQL (у него крайне не стандартный синтаксис);
  • после разворачивания базы полезно сдвигать автоинкременты в таблицах, чтобы в случае обращения по чужому индексу, получать ошибку;
  • в большинстве случаев достаточно зачищать таблицы вместо пересоздания базы, это заметно быстрее.
для некоторых тестов допустимо использовать встраиваемые СУБД (в случае Java, к примеру, H2), но это требует поддержки нескольких СУБД в проекте и плохо увязывается с MySQL (у него крайне не стандартный синтаксис)

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

SQLite совсем про другое. Я подозреваю, что для .NET должны быть адекватные SQL-базы, но они вряд ли годятся для проверки кода, ориентированного на MySQL.
Кстати, MySQL очень медленно разворачивает базу в сравнении с PostgreSQL.

Я конечно тесты не проводил, и не рассматривал вариант с постгрессом, задачу все-таки было необходимо решить в рамках стека технологий проекта.
За наводку спасибо, заинтересовали. Надо будет как-нибудь побаловаться и провести пару экспериментов.
Статья конечно про РСУБД, но тем не менее поделюсь. Используем Mongo — там с этим все достаточно просто и прямо. Развернули новую БД, прогнали init script если есть необходимость, отработал тест, дропнули базу.
У нас есть юнит тесты для слоя доступа — там на каждый тест поднимается отдельная база, и есть acceptance/functional тесты — там кроме БД поднимается еще и инстанс прилолжения и один инстанс приходится на один test suite, иначе таки да, ожидание становится мучительным.
Стек — node.js, js, express, mongoose.

Весь набор тестов не очень большой ~ 50 unit + 150 functional. Отрабатывают секунд за 20 на Core i5/8Gb/SSD. Все тесты и тест сьюты работают исключительно последовательно.
В плане использования одной базы на все тесты с нормальными рсубд MSSQL конечно в этом плане все проще — в TestInitialize создать TransactionScope, в TestCleanup вызвать Dispose на нем, и вообще не надо думать что там тестируемый код с базой делает.

У нас схожие проблемы сравнительно недавно как раз были: и с кучей кода для инициализации одних и тех же тестовых объектов в разных тестах и с тупящей инициализацией базы после того как вся эта инициализация перенеслась в один общий для всех тестов модуль. И все это еще приправлено повторными выполнениями тестов с разными данными через атрибут DataSource. Последнее сильно портило картину, т.к. атрибут DataSource требует данные из той же базы и соотв. надо ее проинициализировать до того как движок запускающий тесты туда полезет за необходимыми значениями, а это происходит до того как срабатывают всякие там TestInitialize и ClassInitialize методы, да и вообще любой код теста, пришлось шаманить.
Добавлю про наш проект.
  • База — SQL Server. На каждый бранч в коде — отдельный инстанс.
  • Кроме «живой» базы, с которой работают разработчики, есть еще так называемый TestDatabaseTemplate — включает только костяк базы без данных, плюс таки данные в таблицах-справочниках. Обновляется база автоматически во время сборки проекта.
  • На каждый отдельно взятый функционал в приложении при AssemblyInitialize создается копия TestDatabaseTemplate с соответствующим суффиксом банально бэкапя TestDatabaseTemplate и восстанавливая его в новую базу, пересоздавая ее, если она уже есть. На всю эту инициализацию тратится примерно 2-3 секунды, так что можно сказать, что все происходит мгновенно :)
  • Есть тесты, что запускаются последовательно, а есть и такие, что запускаются осознанно параллельно с разными таймаутами (сейчас запускаем тест1-тест10 в 10 потоков, спустя n миллисекунд запускаем тест11, а может и еще пару десятков тоже параллельно). Приложение real-time, обрабатывающее таки довольно большие объемы данных, так что и тесты тоже должны симулировать такую вот многопоточность.
  • В solution'е порядка 300+ тестов, из них около 200+ интеграционные, т.е. ходящие в базу (для меня юнит-тест не имеет права ходить в базу — он таки тестирует отдельно взятый класс, максимум небольшую функциональность, где все хождения наружу должны быть закрыты mock'ами). На полный прогон всех тестов тратится порядка 3-5 минут.
А как происходит проверка результата работы метода, который ходит в базу? Если я правильно представил процесс, то результат выполнения может варьироваться от запуска к запуску.

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

Это больше на нагрузочное тестирование похоже.

для меня юнит-тест не имеет права ходить в базу

Да, так и должно быть. У нас они разделены и лежат в разных сборках, одни в *.UnitTests, другие в *.DataBaseIntegrationTests
Спасибо за материал.

Есть несколько вопросов по ожидаемым результатам:
— Где хранится эталонный результат?
— Как решили вопрос со сравнением записей таблиц с большим количеством полей?
Хороший вопрос. Как такого эталонного результата нет. Есть только некоторые характеристики, которым должен отвечать результат выборки.
В качестве примера: если метод должен возвращать непрочитанные сообщения для определенного пользователя, то мы и проверяем в результате, что у объектов свойство IsRead установлено в false, и то, что они принадлежат запрашиваемому пользователю.
А на вход данные генерируются каждый раз.
А юнит-тестирование ли это? Фактически Вы тестируете ещё и взаимодействие с БД. Очевидно, что это интеграционное тестирование
Прошу обратить внимание на вводную часть статьи, где я говорю, что на самом деле это интеграционный тесты, а не модульные.
Ладно, но если помидор обладает «всеми удобствами и преимуществами» огурца, он всё равно остаётся помидором. И называть его огурцом не верно
А нельзя для такого кейса использовать снапшоты с виртаульной машины, с развёрнутой эталонной БД? Один тест — один снапшот? После прохода теста удалить.
Оверхед… Вместо того, чтобы на уже готовом разворачивать новую базу данных, запускать отдельную виртуалку? Ресурсов будет съедаться значительно выше, да и время на копирование/старт виртуалки будет в разы больше, чем создать временную базу данных и пару таблиц в ней.
Работу РСУБД вообще не тестирую, верю что она уже протестированна разработчиками. Тестирую только взаимодействие с интерфейсом используемой РСУБД, что сводится к двум простым шагам:
1. Инкапсулирую все запросы в классы/методы
2. Тестирую получаемый в методе SQL или его часть (на пример условие), без пересылки его в базу

Пока живу в таком контексте и беды не знаю.
Я для Erlang common тестов поднимаю postgresql с данными в tmpfs, если это возможно.
За запуск и останов postgres отвечает само приложение.
Поднимается все быстро. Запуск с кучей миграций (порядка 200 накопилось) проходит за 4 сек (без tmpfs 15 сек).

Таким же образом тестирую opensource типа https://github.com/egobrain/repo
Sign up to leave a comment.