Интеграционное тестирование обязательно т.к. очень много функций (мы это не называем микросервисами, скорее API, хотя по сути очень близко),
Есть такой подход тестировать логику через интеграцию.
Например есть вместо того, чтобы вызвать метод ядра с аргументами мы поднимаем сервис и делает настойщий HTTP запрос (в веб фреймворках для этого есть удобный инструментарий). Он может дотянуться до какойто-части логики, но как только вы попробуете использувать код логики для чего-то другого, то есть шанс поймасть проблем.
Такой подход выглядит очень простым на начальных стадиях разработки. Если у микросервиса одна обязанность то через это его можно протестировать. Как только он начнёт расти и обрастать новыми обязанностями, то любое изменения внутрненних модулей может вызвать поломки (баги или переписывание тестов) на всех внешних.
Сложности интеграционных тестов растёт экспонентциально сложности сервиса. Микросервисы (строгий подход к работе с зависимостями) могут решить эту проблему не давая им разрастаться.
Есть юнит тесты. Есть тесты взаимодействие компонентов между собой (интеграционные, компонентные). Есть тесты на взаимодействие всех компонентов между собой (end to end).
Я под интеграционными именно эту середину и понимаю. Пока для себя еще не сложил в голове чёткого представления, как с ними работать.
Если мы достаточно уверены, что в отдельных компонентах проблем нет, то на этих шагах должно вылезать меньше ошибок и будет не так больно дебажить.
На самом деле очень много зависит от архитектуры. Если проект долгий и меняющийся, то можно вложиться в архитектуру. Тогда логика будут распиханна по модулям, а код который отвечает за взаимодействие между ними будет простым.
Но вот как-то так… И таких задач у меня сплошь и рядом.
Логика сложная но её немного. Много интеграции. Я так понимаю, что задачи с коротким циклом разработки (нет постоянных узменений и улучшений). Интеграционные тесты дорого делать и окупится это только если пилить один и тот-же кусок кода на протяжении долгого времени. А читых юнитов будут немнго как и тольку от них.
А сколько по времени проходит от начала работ до передачи в тестирование и от тестирования до прода?
В таких задачах обязательно нагрузочное тестирование
Связь нагрузочного тестирования и авто-тестов чисто лингвистическая, через слово тест. Решаются разные проблемы, используются разные инструменты, требуются разные компетенции. Они живут в разных плоскостях и между собой никик не сваязанны.
Это да, но если делать упор на управление зависимостями то чистых и почти чистых становится больше.
К примеру придется использовать существующий механизм пересчета из разных валют
Если это внешний модуль по отношению к коду, то его в тестах можно замокать.
При этом если заложить конкретные значения, а этот пересчет в дальнейшем поменяют, тест свалится, что не будет ошибкой, а будет ошибкой теста
Тест это вещь в себе. И входящие занчения и результат известны стразу. Если результат теста зависит от состояния внешней системы, то это плохой тест: он упадёт, без изменений в коде.
Тут важно понять, что именно хочется протестировать. Если работу интеграции и вашего кода вместе взятых, то тут не юниты нужны. Если то как ведёт себя код при получении определённых данных, то просто нужно замокать поставщика данных. Может оказаться что логика именно этого кода сильно тривиальна и юнит-тесты не принесут какой-либо пользы. А вот если есть ветвление или обработка ошибок, то можно добавить и тестов. Чем проще логика, тем меньше тестов. Если тест укладывается в пару строк то писать его быстро.
Тут правдо нужно сделать оступление и сказать, что я пишу на Python с библиотекой pytest. Это очень мощная библиотека, после неё с Junit5 в Джаве чувствуешь себя как будто в каменный век вернулся.
Вот тут юниттестам и конец. Это уже интеграция. Я и сам долго считал, что юниты это сложно, пока не понял, что на самом деле это были интеграционные тесты которые пытались тестировать инварианты в логике кода. Такие тесты зло и трата времени.
примеру пишем код который получает сумму проводок по клиенту в какой-то валюте.
Если есть чистая функция получает набор сум и курс валют в виде аргументов, а потом складывает и умножает и возвращает рузультат. То можно написать юниттест который прверяет, что складываются и умножаются нужные цифры.
Если бинзнес логика идёт в перемешку с кодом который генерирует SQL, выполняет запросы в базу и разбирает ответы, то юнит тесты здесь не применимы.
Я начал строить даже небольшие автономные кусочки кода (AWS Lambda, Python) через архитектуру (разделять получение и обработку данных) и остался доволен. Когда собираешь приложение из протестированных кусочков, то интеграционные тесты на взаимодействие этих кусочков не очень сложные (они проверяют что модуль А дёрнул метод модуля В). А на энве ловишь в основном интеграционные проблемы с внешними сервисами.
И сколько вы реальных ошибок смогли найти используя тесты
Чтобы написать тест нужно понимать какой конкретно результат нужно получить. Чтобы код писать достаточно абстрактного представления о том, что должна делать программа. Разницу в поверхностом и детальном понимании проблемы сложно померить в багах.
Лапшу сложно тестировать, надо нарезать на модули. Удобная архитектура уменьшает количество багов сама по себе.
Если писать юниттесты вместе с кодом, то ложные срабатывания и баги в основном исправляются до коммитов.
Запуск тесты намного быстрее запуска программы, когда пишешь код то получаешь очень быстрый фидбэк, это экномит время на разработку.
Я достаточно сильно разбиваю код на модули и юнит-тесты у меня в среднем 3-5 строк с быстрым выполнением.
В целом, основная проблема с юнит-тестами состоит в том, что надо вовремя останавливаться. Если логика кода не имеет смысла в отрыве от мира, то писать для него юнит-тест — отличный пример вкачать -цать уровней в моках и ничего хорошего не получить.
Если модуль тесно связан с другими модулями, то юниттесты будут полны моков и боли. Тут скорее в сторону убирания связанности стоит смотреть.
А между интеграционными тестами и юнит-тестами реально есть некоторое провисание
Это же прекрасно. Хуже когда ахитектура позволяет писать юниты только размером с интеграционные.
> В питоне нужны тесты для проверки инвариантов (что все if/else сохраняют возвращаемый тип)
И в логике нужны тесты на проверку инвариантов. Если в типах проблемы, то тесты это тоже поймают.
> но вот гоняться за нелепыми опечатками в exception'ах — уже не нужно
Это тоже ветвление логики. Исключение внутри обработки исключения одна из самых частых ошибок при слабом автотестировании. По хорошему туда тоже стоит заглянуть тестами.
> Мало того, что обработка ошибок (которую трудно проверить),
Связка pytest + monkeypatch + Mock + side_effect хорошо справляется. Код который кидает исключение нужно вынести в отдельную функцию и её замокать.
Было плохо, стало плохо. Но с интерфейсами и инверсией зависимостей.
Можно ли сказать что метод который читает данные из провайдера и проводит вычисления следует принципу единой ответственности?
В данном случае напрашивается чистая функция которая отвечает за вычисления. Она принимает два аргумента (координаты и смещение) и возвращает результат.
Писать тесты к таким функциям одно удовольствие. Ведь у неё нет зависимости и сайдэфектов. И моков никаких не надо.
Клас с бизнес логикой будет простым как пробка. Взять из сети координаты, передать их в вычисление. Тут без моков не протестить. Но и тестить то особо нечего: тест на позитивный сценарий и пара тестов на обработку ошибок. Тут будет прямо как в статье «Юнит-тесты зависят от подробностей реализации». Если функция имеет сайдэфекты, то их надо тестировать.
У нас есть a и b. Для их чтения используются разные инструкции (LOAD_FAST, LOAD_GLOBAL).
При построении байткода если есть локальная переменная, все обращения к ней локальны (LOAD_FAST), вне зависимости от порядка их следования.
flake8 — это не совсем анализатор, это обёртка над анализаторами. А еще его можно расширять плагинами. Если сравнивать flake8 с плагинами с голым pylint, то новых ошибок будет меньше.
В свое время выбрал flake8 за возможность точной настройки отключения проверок. Была огромная кодовая база и расставлять комментарии на файлы и строчки не хотелось. Я просто прошёлся по отчету ошибок и отключил конкретные проверки в конкретных файлах. Это позволило быстро сконфигурировать CI и не давать этой заразе расползаться.
А вот пример из проекта на Джанго: никаких ложных срабатываний на отсутствие докстрингов нет.
Есть такой подход тестировать логику через интеграцию.
Например есть вместо того, чтобы вызвать метод ядра с аргументами мы поднимаем сервис и делает настойщий HTTP запрос (в веб фреймворках для этого есть удобный инструментарий). Он может дотянуться до какойто-части логики, но как только вы попробуете использувать код логики для чего-то другого, то есть шанс поймасть проблем.
Такой подход выглядит очень простым на начальных стадиях разработки. Если у микросервиса одна обязанность то через это его можно протестировать. Как только он начнёт расти и обрастать новыми обязанностями, то любое изменения внутрненних модулей может вызвать поломки (баги или переписывание тестов) на всех внешних.
Сложности интеграционных тестов растёт экспонентциально сложности сервиса. Микросервисы (строгий подход к работе с зависимостями) могут решить эту проблему не давая им разрастаться.
Есть юнит тесты. Есть тесты взаимодействие компонентов между собой (интеграционные, компонентные). Есть тесты на взаимодействие всех компонентов между собой (end to end).
Я под интеграционными именно эту середину и понимаю. Пока для себя еще не сложил в голове чёткого представления, как с ними работать.
Если мы достаточно уверены, что в отдельных компонентах проблем нет, то на этих шагах должно вылезать меньше ошибок и будет не так больно дебажить.
На самом деле очень много зависит от архитектуры. Если проект долгий и меняющийся, то можно вложиться в архитектуру. Тогда логика будут распиханна по модулям, а код который отвечает за взаимодействие между ними будет простым.
Логика сложная но её немного. Много интеграции. Я так понимаю, что задачи с коротким циклом разработки (нет постоянных узменений и улучшений). Интеграционные тесты дорого делать и окупится это только если пилить один и тот-же кусок кода на протяжении долгого времени. А читых юнитов будут немнго как и тольку от них.
А сколько по времени проходит от начала работ до передачи в тестирование и от тестирования до прода?
Связь нагрузочного тестирования и авто-тестов чисто лингвистическая, через слово тест. Решаются разные проблемы, используются разные инструменты, требуются разные компетенции. Они живут в разных плоскостях и между собой никик не сваязанны.
Это да, но если делать упор на управление зависимостями то чистых и почти чистых становится больше.
Если это внешний модуль по отношению к коду, то его в тестах можно замокать.
Тест это вещь в себе. И входящие занчения и результат известны стразу. Если результат теста зависит от состояния внешней системы, то это плохой тест: он упадёт, без изменений в коде.
Тут важно понять, что именно хочется протестировать. Если работу интеграции и вашего кода вместе взятых, то тут не юниты нужны. Если то как ведёт себя код при получении определённых данных, то просто нужно замокать поставщика данных. Может оказаться что логика именно этого кода сильно тривиальна и юнит-тесты не принесут какой-либо пользы. А вот если есть ветвление или обработка ошибок, то можно добавить и тестов. Чем проще логика, тем меньше тестов. Если тест укладывается в пару строк то писать его быстро.
Тут правдо нужно сделать оступление и сказать, что я пишу на Python с библиотекой pytest. Это очень мощная библиотека, после неё с Junit5 в Джаве чувствуешь себя как будто в каменный век вернулся.
Вот тут юниттестам и конец. Это уже интеграция. Я и сам долго считал, что юниты это сложно, пока не понял, что на самом деле это были интеграционные тесты которые пытались тестировать инварианты в логике кода. Такие тесты зло и трата времени.
Если есть чистая функция получает набор сум и курс валют в виде аргументов, а потом складывает и умножает и возвращает рузультат. То можно написать юниттест который прверяет, что складываются и умножаются нужные цифры.
Если бинзнес логика идёт в перемешку с кодом который генерирует SQL, выполняет запросы в базу и разбирает ответы, то юнит тесты здесь не применимы.
Я начал строить даже небольшие автономные кусочки кода (AWS Lambda, Python) через архитектуру (разделять получение и обработку данных) и остался доволен. Когда собираешь приложение из протестированных кусочков, то интеграционные тесты на взаимодействие этих кусочков не очень сложные (они проверяют что модуль А дёрнул метод модуля В). А на энве ловишь в основном интеграционные проблемы с внешними сервисами.
Чтобы написать тест нужно понимать какой конкретно результат нужно получить. Чтобы код писать достаточно абстрактного представления о том, что должна делать программа. Разницу в поверхностом и детальном понимании проблемы сложно померить в багах.
Лапшу сложно тестировать, надо нарезать на модули. Удобная архитектура уменьшает количество багов сама по себе.
Если писать юниттесты вместе с кодом, то ложные срабатывания и баги в основном исправляются до коммитов.
Запуск тесты намного быстрее запуска программы, когда пишешь код то получаешь очень быстрый фидбэк, это экномит время на разработку.
Я достаточно сильно разбиваю код на модули и юнит-тесты у меня в среднем 3-5 строк с быстрым выполнением.
Если модуль тесно связан с другими модулями, то юниттесты будут полны моков и боли. Тут скорее в сторону убирания связанности стоит смотреть.
Это же прекрасно. Хуже когда ахитектура позволяет писать юниты только размером с интеграционные.
> В питоне нужны тесты для проверки инвариантов (что все if/else сохраняют возвращаемый тип)
И в логике нужны тесты на проверку инвариантов. Если в типах проблемы, то тесты это тоже поймают.
> но вот гоняться за нелепыми опечатками в exception'ах — уже не нужно
Это тоже ветвление логики. Исключение внутри обработки исключения одна из самых частых ошибок при слабом автотестировании. По хорошему туда тоже стоит заглянуть тестами.
> Мало того, что обработка ошибок (которую трудно проверить),
Связка pytest + monkeypatch + Mock + side_effect хорошо справляется. Код который кидает исключение нужно вынести в отдельную функцию и её замокать.
Это придумали консультанты чтобы свои услуги продавать!
А еще к линтеру нужен автоматический-запускатель. Я использую pre-commit локально и github actions на гитхабе.
Под свои предпочтения собрал шаблон проекта с плагинами.
github.com/Cjkjvfnby/project_template/tree/master/%7B%7Bcookiecutter.folder_name%7D%7D
Можно ли сказать что метод который читает данные из провайдера и проводит вычисления следует принципу единой ответственности?
В данном случае напрашивается чистая функция которая отвечает за вычисления. Она принимает два аргумента (координаты и смещение) и возвращает результат.
Писать тесты к таким функциям одно удовольствие. Ведь у неё нет зависимости и сайдэфектов. И моков никаких не надо.
Клас с бизнес логикой будет простым как пробка. Взять из сети координаты, передать их в вычисление. Тут без моков не протестить. Но и тестить то особо нечего: тест на позитивный сценарий и пара тестов на обработку ошибок. Тут будет прямо как в статье «Юнит-тесты зависят от подробностей реализации». Если функция имеет сайдэфекты, то их надо тестировать.
6 0 LOAD_GLOBAL 0 (print)
2 LOAD_GLOBAL 1 (b)
4 CALL_FUNCTION 1
6 POP_TOP
7 8 LOAD_GLOBAL 0 (print)
10 LOAD_FAST 0 (a)
12 CALL_FUNCTION 1
14 POP_TOP
8 16 LOAD_FAST 0 (a)
18 LOAD_CONST 1 (10)
20 INPLACE_ADD
22 STORE_FAST 0 (a)
24 LOAD_CONST 0 (None)
26 RETURN_VALUE
У нас есть a и b. Для их чтения используются разные инструкции (LOAD_FAST, LOAD_GLOBAL).
При построении байткода если есть локальная переменная, все обращения к ней локальны (LOAD_FAST), вне зависимости от порядка их следования.
В свое время выбрал flake8 за возможность точной настройки отключения проверок. Была огромная кодовая база и расставлять комментарии на файлы и строчки не хотелось. Я просто прошёлся по отчету ошибок и отключил конкретные проверки в конкретных файлах. Это позволило быстро сконфигурировать CI и не давать этой заразе расползаться.
А вот пример из проекта на Джанго: никаких ложных срабатываний на отсутствие докстрингов нет.
per-file-ignores =
*/tests/*:D102,D103,S101
*/manage.py:D103,C812
*/settings/*:F401,F403,F40
Если поставить на паузу и идти по шагам, там будет коротки пояснительный текст.