LINUX.ORG.RU

Как тестировать stateful функции и не повеситься?

 , , ,


0

2

Здравствуйте! Вопрос, собственно, как тестировать stateful функции? А то как тестировать stateless функции я знаю: вот входные данные, вот результат — и пусть тестировочный фреймворк сверяет. Но вот как тестировать функции, которые должны обращаться к бд или общаться с клиентом по сети? Как удостовериться, что функция сделает именно то, что мне нужно, а не, допустим, дропнет таблицу или ещё что? У меня не особо сложное веб-приложение, но чтобы удостовериться, что всё работает как надо, я часами сижу в дебаг моде и вручную проверяю каждый запрос и каждую переменную. И это я пока не вводил многопоточку, хотя планирую, но пока просто боюсь это дебажить.

Пж, киньте в меня мануалом по написанию автотестов для stateful. Я пытался загуглить, но мне попадаются только статьи по типу «тестировать важно, но как это делать мы не скажем». Любому совету буду благодарен.

P.S. Программирую на C++ с использованием библиотеки Boost.

для начала надо стараться возложить проверки на статическую типизацию, проверку инвариантов, в данном случае корректности запросов к бд.

тестирование и дебаг - это последний шанс поймать ошибку в неверном софте, а надо стараться писать софт изначально корректный, пользуясь средствами языка.

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

alysnix ★★★
()
Ответ на: комментарий от anonymous

главное, что в с++ изоляция подсистем в частности бд, делается элементарно, до уровня выдачи интерфейсов работы с базой, типа ридонли операции, операции записи, критические операции и прочее.

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

на плюсах это пишется легко и приятно.

alysnix ★★★
()

функция сделает именно то, что мне нужно, а не, допустим, дропнет таблицу или ещё что?

К чему задавать такие вопросы? Если в коде нет дропа таблицы, то таблицу она не дропнет.

как тестировать функции, которые должны обращаться к бд

Типа нигде, где вы учились писать тесты не было «как тестировать функции, которые должны обращаться к бд»? Да, это важный вопрос, все должно быть.

общаться с клиентом по сети?

Нужно больше информации. Возможно вам просто нужны mock объекты или стабы или как оно называется в вашем фреймворке.

goingUp ★★★★★
()

вот входные данные, вот результат — и пусть тестировочный фреймворк сверяет Но вот как тестировать функции, которые должны обращаться к бд или общаться с клиентом по сети?

Абсолютно так же, в чём ты видишь проблему?

Прежде всего, то что ты перечислил ортогонально statefulness’у. Тестовый фреймворк поднимает базу с нужными данными или http сервис в нужном состоянии - это твои входные данные (фикстура), в тестах запускаешь на нём свой код, получаешь от него результат и сравниваешь. При этом stateful может быть структура с геттером и сеттером, безо всяких баз и http.

Но и тестирование stateful от stateless отличается только промежуточными шагами, мутирующими стейт. Некоторые фреймворки умеют удобно писать сценарии для таких тестов, например https://github.com/catchorg/Catch2/blob/devel/docs/test-cases-and-sections.md#bdd-style-test-cases

anonymous
()

Чаще всего мокать там нечего, если речь идёт об одном запросе. Поэтому очищаешь бд, кодом вставляешь данные и гоняешь какие хочешь тесты.

Не пытайся протестировать всё. Тесты — это как система типов, у них общая цель: обеспечить корректность. Но система типов не может заранее предотвратить абсолютно любую ошибку. Тесты тоже не всесильны.

kaldeon
()

Удивительно что никто не предложил переписывать всё на хаскеле.

Ну а в целом выше уже ответили, поддержу версию про mock и test fixture. Готовим одноразовое окружение со всеми stateful-делами, прогоняем тесты, и выбрасываем ошметки окружения.

Возможно внутри фреймворка уже предусмотрен стандартный способ такое сделать, не знаю чего ожидать от веб-приложений на boost.

melkor217 ★★★★★
()
Ответ на: комментарий от anonymous

For every annotated function, a new test database is created so tests can run against a live database but are isolated from each other.

Нахрена создавать новую БД для каждого теста? Создаёшь тестовую БД один раз, прогоняешь миграции, а потом каждый отдельный тест запускаешь внутри БД-транзакции, в конце теста транзакцию откатываешь — вот тебе и изоляция от других тестов. Ты даже можешь несколько тестов запускать одновременно, изменения в одной тестовой транзакции не будут видны в другой.

theNamelessOne ★★★★★
()
Последнее исправление: theNamelessOne (всего исправлений: 2)
Ответ на: комментарий от theNamelessOne

Это оптимизация для частного случая. В общем случае тестируемый код использует транзакции сам, а вложенных транзакций с обычным begin/commit синтаксисом не умеет ни одна субд.

anonymous
()
Ответ на: комментарий от anonymous

В общем случае тестируемый код использует транзакции сам, а вложенных транзакций с обычным begin/commit синтаксисом не умеет ни одна субд.

Зато вложенные транзакции умеют «нормальные фреймворки» — ты в коде пишешь транзакцию, в неё вкладываешь ещё одну транзакцию и т.д. и т.п., а фреймворк уже сам разберётся при генерации SQL, надо ли посылать СУБД команду BEGIN или SAVEPOINT savepoint1, ROLLBACK или ROLLBACK TO SAVEPOINT savepoint1.

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

theNamelessOne ★★★★★
()
Ответ на: комментарий от anonymous

Перед тестами подготавливаешь базу данных, после тестов - чистишь.

Нормальные фреймворки занимаются этим сами

Это невозможно в принципе, потому что никто кроме программиста не знает, что должно быть в базе данных для тестов. Так как база данных не является частью вашей программы, то нет магического автоматического способа сохранения/восстановления её состояния. Даже если вы собрались использовать транзакции (если вы не отлаживаете сами транзакции), кто-то должен послать команду её начала.

raspopov
()

Точно также как и stateless.

Фактически любое чтение состояния становится скрытыми входными параметрами функции.

А любая запись - такими же скрытыми выходными.

unDEFER ★★★★★
()
Ответ на: комментарий от raspopov

Автоматизация возможна, если пользоваться какими-то общими паттернами или даже фреймворками.

Если код пишется одной большой колбасой «по наитию», то там и строительные леса для поддержки тестов тоже придётся велосипедить самостоятельно от и до.

Chiffchaff
()

Но вот как тестировать функции, которые должны обращаться к бд

Тестовая бд, которую тест проверяет после отработки функции.

общаться с клиентом по сети

Тестовый локальный запуск сервера и клиента в процессе теста.

rumgot ★★★★★
()
Ответ на: комментарий от theNamelessOne

Это не «нормальные» или «ненормальные» фреймворки, это просто разные подходы. В ORM или query builder такое возможно, когда пишутся запросы руками - нет.

anonymous
()
Ответ на: комментарий от anonymous

а вложенных транзакций с обычным begin/commit синтаксисом не умеет ни одна субд.

Microsoft SQL Server Поддерживает вложенные транзакции, позволяя выполнять BEGIN TRANSACTION внутри других транзакций.

IBM Db2 Поддерживает вложенные транзакции, что позволяет выполнять транзакции внутри других транзакций.

Psilocybe ★★★★★
()
Ответ на: комментарий от goingUp

Если в коде нет дропа таблицы, то таблицу она не дропнет.

Вовсе не факт. Завершился IOCCC'24 (комментарий) вот тут например совсем не видно тех текстов которые оно печатает.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от theNamelessOne

каждый отдельный тест запускаешь внутри БД-транзакции, в конце теста транзакцию откатываешь

Видел три подхода

  • каждый тест в транзакции и откатывается (самый быстрый наверное, но случайный коммит транзакции запорет все тесты)
  • для каждого теста пересоздается база или хотя бы отдельные таблицы
  • ты тестируешь только то, что только что вставил, если например нужно протестировать выборку юзера по логину, ты вставляешь юзера с случайным логином (чтобы повторные запуски теста не выкидывали ошибку уникального ключа) и выбираешь его. В таблице юзеров будут предыдущие записи, но тем лучше (наверное).
goingUp ★★★★★
()
Последнее исправление: goingUp (всего исправлений: 2)
Ответ на: комментарий от anonymous

Это невозможно в принципе

Я привёл ссылку, или и почитай как оно работает.

Я, естественно, прочитал прежде чем ответить, а вот вы читали? Там описан точно такой же способ - программист сам вручную должен заполнить базу данных до тестов, и очистить после. А весь сервис заключён в атрибуте, который похоже предлагает внеязыковой синтаксис подключения к базе данных, что выходит за рамки темы.

raspopov
()

test-containers в общем случае. Если же БД поддерживает встраивание/in memory режим (например, как sqlite), можно просто создавать подключение к временной БД в тестах.

Ещё можно декомпозировать примитивные операции над БД и вручную протестировать их, а сложную бизнес логику тестировать на моках этих операций, которые всё делают в ОЗУ.

С внешними сервисами аналогично. Либо поднимать мок-сервис, либо делать интерфейс клиента этого сервиса и настояющую версию тестировать руками, а в другие части приложения в тестах прокидывать мок.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 2)