Pull to refresh

Comments 28

Правильно ли я понимаю, если бизнеслогика где-то в гуще кода делает подтверждение транзакции, то никакого отката не произойдет (в том числе на глубине) по подверженной транзакции, и как результат состояние бд для следующих тестов будет отличаться?

Бизнес-логика в тестах имеет доступ только к внутренней транзакции. После коммита/роллбэка будет создана новая вложенная транзакция и тест продолжится.

В конце теста произойдет откат внешней транзакции и изменения, сделанные во внутренних транзакциях, будут сброшены.

Вообще как говорится, it depends. Во-первых, от движка БД, во-вторых, от бизнес логики. Если мне не изменяет память в MSSQL нет явной поддержки вложенных транзакций например (по крайней мере года 4 назад) и если внутри бизнес логики был Rollback Transaction, то он откатить и нашу внешнюю транзакцию если @@TRANCOUNT станет равным 0 - например так:

BEGIN TRANSACTION -- outer
EXECUTE TEST_SP
ROLLBACK TRANSACTION -- outer

...

PROCEDURE TEST_SP
BEGIN
BEGIN TRANSACTION -- inner
...
ROLLBACK TRANSACTION --inner
END

В этом случае - за последствия не отвечаем

Если мне не изменяет память, то откат распределенных транзакций в mssql также невозможен.

Например, сюда попадают прилинкованные бд (через odbc как вариант)

Интересно есть-ли в природе базы данных, которые поддерживают nested transactions (или autonomous subtransactions) в таком синтаксисе?

SQLAlchemy всю жизнь использует savepoints для иммитации вложенных транзакций. Со стороны кода все равно красиво, но на практике результат сильно зависит от конкретного движка и характера нагрузки (например https://postgres.ai/blog/20210831-postgresql-subtransactions-considered-harmful).

UFO just landed and posted this here

Это относительная величина.

Для упрощения расчетов предположим, что создание сотни таблиц, а затем их удаление на рабочем компьютере занимает ровно 1 секунду. Допустим так же, что в приложении 6000 тестов. Таким образом одно только создание и удаление займет 6000 секунд (больше часа!).

"Ускорим" операцию создания/удаления до 0.1 секунды. Даже так десять минут на создание/удалении схемы.

Да, для "ночных" пайплайнов десять минут — незначительная величина, но если у вас тесты запускаются на каждый пуш в ветку, то получается как-то неэффективно.

UFO just landed and posted this here

Более того, это и есть правильный подход к написанию тестов. Одно из основных правил гласит: тесты должны быть независимы друг от друга. Это значит, что тесты могут запускаться на базе данных с любыми данными внутри, в том числе, и от предыдущих тестов.

Все зависит от характера использования базы. В OLAP база является скорее частью модели, где логика зашита непосредственно в самих данных. Поэтому тесты в изоляции слаборепрезентативны. Я думаю, что не стоит зацикливаться только на одном виде тестирования.

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

Кажется слишком ограниченным выводом из требования независимости.


Это так же может означать, что мы включаем в тест создание пустой базы, и нам тогда наплевать, что там было до того.
И опять же, как вы собираетесь проверять ситуацию вида "и после этого в таблице ХХХ не осталось записей" если в ней есть данные с предыдущих тестов? Можно конечно заменить на менее строго утверждение "и по ключам созданным в тесте не осталось строк", а ключи сделать свежими GUIDами, чтобы они точно не пересеклись с существующими данными, но не всегда это возможно/удобно

Ситуация вида "и после этого в таблице ХХХ не осталось записей", как и многие другие, похожие, решается введением понятия "Tenant". Это позволяет иметь одну базу данных, с данными от предыдущих тестов или чего угодно другого, но при этом каждый новый тест будет жить в своём мирке, который создаёт под себя на этапе подготовки данных.

А физически это просто еще одна колонка в каждой таблице БД? И в этой колонке будет ключ сеанса теста, верно?
Я в мире 1С кручусь, у нас тут исторически сильная связь с БД, и БД практически не мокается в большом числе сценариев.
Ближайший аналог — разделитель данных. но он работает глобально на все таблицы, в итоге — развернуть базу с эталонными данными из шаблона зачастую быстрее, чем создать новую область с разделителем и заполнить её всеми параметрами окружения, необходимыми для теста и общими для всех тестов.
Но это все равно дорого. В итоге вполне работает транзакционный подход для "юнитов" и разворачивание базы из шаблона каждый раз для сценариев.

Tenant - это не сеанс теста, но физически - да, это ещё одна колонка в одной или нескольких таблицах, не обязательно всех, чтоб все данные можно было разделить "перегородкой" на отдельные, независимые мирки.

Я не сильно знаком с миром 1С, поэтому у меня нет понимания , почему это дороже, вам виднее, конечно.

Потому, что у вас это "или нескольких", а в 1С — обязательно всех.
В итоге такие неизменные в большинстве тестов, но участвующие в них справочники как: организации, валюты, учетные политики и тп служебные вещи, которые должны быть заполнены для проведения документа (как основной тестируемой операции в связанных с БД тестах) нужно будет заполнять каждый раз заново.

UFO just landed and posted this here

Если есть необходимость пишутся тесты под сам репозиторий. Обычно это тестирование специфических методов.

UFO just landed and posted this here

Вы упустили https://www.testcontainers.org/ , которые позволяют поднимать docker container с нужной бд.

Для ускорения тестов можно использовать mount на tmpfs и использовать backup вместо выполнения миграций на каждый тест.

UFO just landed and posted this here

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

Я в свое время такую идею сразу же отбросил, но тогда и докер контейнеры только начинали зарождаться.

Вы уверены, что приведенные примеры в принципе относятся к unit-тестированию?

В качестве юнита можно взять как один класс, так и их связанную группу. И проверить работу бизнес-логики (и только бизнес-логики) в изоляции от особенностей конкретного хранилища, накладных расходов (=время теста), а также недетерминированности (отвал бд - точка отказа)

Ваши примеры я бы назвал чуть иначе - интеграционное тестирование. Впрочем, вполне могу быть и не прав

В этой области с терминологией все не однозначно: unit, component, integration, system. Название скорее зависит от цели тестирования, нежели фактической структуры SUT (system under test), хотя определенная корреляция все же просматривается.

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

Хорошая статья, я в свое время тоже занимался этим вопросом https://habr.com/ru/company/arcadia/blog/304322/ но пришел к другому пути. Я для каждого теста создаю копию только необходимой части БД, это позволяет экономить кучу времени и решает некоторые проблемы с параллельным запуском тестов в рамках одного прохода.

Sign up to leave a comment.