Думаю, будет интересно ознакомиться, особенно участникам обсуждения тестирования в соседнем топике — каждая из сторон увидит в статье элементы своего подхода к тестированию
Сразу добавлю, что в статье моки все же есть — в виде так называемых "Nullables"
Здравствуйте, Буравчик, Вы писали:
Б>Встретил интересную статью про тестирование. Статья хорошо структурирована, достаточно понятно доносит заложенные идеи и имеет примеры кода.
Я не понял в чём новшество и чем это отличается от моков. Это какая-то специфическая проблема экосистемы JavaScript что-ли?
The tests use a Nullable CommandLine to throw away stdout and Configurable Responses to provide pre-configured command-line arguments. They also use Output Tracking to see what would have been written to stdout.
Это полный аналог чего какой-нибудь Mockito.mock(CommandLine.class) и сделает в Java. Но не требуется ничего сувать в прод-код, все эти configurable resonses ( аналогом будет when().thenReturn() ) и tracking делается в тестовом коде (verify/ArgumentCaptor). Преимущество хотя бы в том, что в прод-артефакты никакая тестовая петрушка не попадает, но при этом можно мокать даже чужие библиотеки.
Серьёзный недостаток — вот забыли они в своём CommandLine.createNull предусмотреть, что write() оказывается умеет кидать исключение, и всё, невозможно протестировать такой сценарий без того чтобы патчить прод-код.
Overlapping Sociable Tests... For example, imagine the dependency chain LoginController → Auth0Client → HttpClient:
— это то что я пытаюсь объяснить. Вот тут они оборвали цепочку на HttpClient не просто так, а потому что HttpClient лазит в сеть и требует ресурсы (Zero-Impact Instantiation... Don’t connect to external systems, start services, or perform long calculations.). Так что HttpClient придётся мокать (аналог моих тестов с моками репозитория), а потом отдельно тестировать The HttpClient tests check that HttpClient is correct, including using Narrow Integration Tests to check how it communicates with HTTP servers. (аналог моих репо+бд тестов или conformance).
Parameterless Instantiation... Ensure all classes have a constructor or factory that doesn’t take any parameters — серьёзно? Зачем responsibility смешивать? Классам классово, а инициализация отедяется в composition root. This test-specific factory method is easiest to maintain if it’s located in the production code next to the real constructors. Мде.. SRP, не слышали?.. Видимо в javascript не хватает вменяемой IDE и статической типизации.
... дальше потом почитаю, большая статья.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Буравчик, Вы писали:
Б>Встретил интересную статью про тестирование.
Спасибо, согласен, что интересная.
Пролистал по диагонали — мне кажется если бы он поширше это написал, сразу получилась бы книжка, которую можно и купить Понравилось про тестирование через structured logging — я так е2е тесты писал для цепочки из нескольких микросервисов. Сервисы пишут structured логи в ELK, а тест запускает транзакцию, и потом мониторит логи по поводу происходящего внутри этой транзакции. В логах пишется когда дошли до какой-то фазы, и релевантные ID, которые тест оттуда вытягивает — и проверяет что там нужные штуки создались, перешли в правильное состояние, и т.д.
Но ответа на вопрос как по-быренькому, без поднятия реальной базы, протестировать что все SQL-запросы хотя бы синтаксически валидны я в статье (ожидаемо) не увидел
Здравствуйте, ·, Вы писали:
·>Это полный аналог чего какой-нибудь Mockito.mock(CommandLine.class) и сделает в Java. Но не требуется ничего сувать в прод-код, все эти configurable resonses ( аналогом будет when().thenReturn() ) и tracking делается в тестовом коде (verify/ArgumentCaptor).
Там во вступлении автор говорит, что when/thenReturn и verify/ArgumentCaptor гвоздями прибивают тесты к деталям реализации и нафиг они нужны такие тесты. Как я понимаю, простейшая иллюстрация:
1. Можно verify/ArgumentCaptor, что код вызвал PrintStream::printf(). Но так делать бестолково, потому что тестируется реализация, а не эффект. Тот же самый принт могли сделать через println() например. За всеми этими методами следить чтоли?
2. Лучше: подсунуть в код PrintStream, который просто соберёт весь аутпут, и потом его проверить. Это хоть и лучше, но всё равно завязано на реализацию.
3. Ещё лучше: на каждую логическую операцию вывода чего-то на консоль — записывать куда-то во внутренний лог сообщения типа "выведено приветствие", "выведен копирайт", "выведена ошибка 453". В тесте потом смотришь на этот лог и этим ограничиваешься. Что там стоит за каждым событием — то ли PrintStream::printf(), то ли PrintStream::println(), то ли вообще десяток write(), то ли оно вообще это звуком юзеру говорит — и пофигу, тест не меняется.
Здравствуйте, rosencrantz, Вы писали:
R>·>Это полный аналог чего какой-нибудь Mockito.mock(CommandLine.class) и сделает в Java. Но не требуется ничего сувать в прод-код, все эти configurable resonses ( аналогом будет when().thenReturn() ) и tracking делается в тестовом коде (verify/ArgumentCaptor). R>Там во вступлении автор говорит, что when/thenReturn и verify/ArgumentCaptor гвоздями прибивают тесты к деталям реализации
Ну альтеранивой предлагается, что "вызыван writeOutput(text)". Разница-то в чём?
R> и нафиг они нужны такие тесты. Как я понимаю, простейшая иллюстрация: R>1. Можно verify/ArgumentCaptor, что код вызвал PrintStream::printf(). Но так делать бестолково, потому что тестируется реализация, а не эффект. Тот же самый принт могли сделать через println() например. За всеми этими методами следить чтоли?
А там не был PrintStream. У него специальный тип CommandLine был введён с двумя методами, который и мокается, правда вставкой мусора в прод-код. Аргументация "не используйте моки" — хромает, уровня straw-man. Как он собирался в принципе делать nullables для jdk-класса PrintStream?!
R>2. Лучше: подсунуть в код PrintStream, который просто соберёт весь аутпут, и потом его проверить. Это хоть и лучше, но всё равно завязано на реализацию.
Ну verify/ArgumentCaptor ровно это и делает. Просто уже готовая либа, которая собирает аутпут и т.п. Вместо этого предложено запихать в прод код какой-то EventEmitter и "this._emitter.emit(OUTPUT_EVENT, text);" — вот эта грязь останется и в прод-коде! Притом довольно убогая: пример с броском исключения — рассказал.
R>3. Ещё лучше: на каждую логическую операцию вывода чего-то на консоль — записывать куда-то во внутренний лог сообщения типа "выведено приветствие", "выведен копирайт", "выведена ошибка 453". В тесте потом смотришь на этот лог и этим ограничиваешься.
А зачем? Логи это так, для дебага в основном, да и меняться могут. А самая главная беда — факт вывода в лог вовсе не означает что действие действительно совершено. надо именно ассертить реальные действия.
R>Что там стоит за каждым событием — то ли PrintStream::printf(), то ли PrintStream::println(), то ли вообще десяток write(), то ли оно вообще это звуком юзеру говорит — и пофигу, тест не меняется.
О PrintStream — там всё просто, это легковесный класс с Zero-Impact Instantiation, неясно зачем его вообще мокать (как Pauel зачем-то предлагал мокать hmac в начале беседы в соседней теме). И код с ним можно делать через то что там называют "Overlapping Sociable Tests".
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, rosencrantz, Вы писали:
Б>>Встретил интересную статью про тестирование. R>Спасибо, согласен, что интересная.
Мне интересно показалось введение более популярной терминологии для объяснения тонких моментов, например "Overlapping Sociable Tests". Я их называл тестами модулей (кусков composition root) но похоже это мало кому понятно.
Но в целом — старые песни о главном, новыми словами.
R>Пролистал по диагонали — мне кажется если бы он поширше это написал, сразу получилась бы книжка, которую можно и купить
Дык он и продаёт, курсы и тренинги На книгах нынче не заработаешь.
R>Понравилось про тестирование через structured logging — я так е2е тесты писал для цепочки из нескольких микросервисов. Сервисы пишут structured логи в ELK, а тест запускает транзакцию, и потом мониторит логи по поводу происходящего внутри этой транзакции. В логах пишется когда дошли до какой-то фазы, и релевантные ID, которые тест оттуда вытягивает — и проверяет что там нужные штуки создались, перешли в правильное состояние, и т.д.
По-моему такое годится только для закрытия дыр в операционном дизайне системы.
R>Но ответа на вопрос как по-быренькому, без поднятия реальной базы, протестировать что все SQL-запросы хотя бы синтаксически валидны я в статье (ожидаемо) не увидел
Да так же, как и я говорил: "For databases, access a real database". Т.е. тестами репо+бд, которые он назвал Narrow Integration Tests.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Здравствуйте, rosencrantz, Вы писали:
R>>Понравилось про тестирование через structured logging — я так е2е тесты писал для цепочки из нескольких микросервисов. Сервисы пишут structured логи в ELK, а тест запускает транзакцию, и потом мониторит логи по поводу происходящего внутри этой транзакции. В логах пишется когда дошли до какой-то фазы, и релевантные ID, которые тест оттуда вытягивает — и проверяет что там нужные штуки создались, перешли в правильное состояние, и т.д. ·>По-моему такое годится только для закрытия дыр в операционном дизайне системы.
А как было бы правильно? Вся система построена более менее вокруг одно типа транзакций — из внешней системы приходит ивент, в ответ на ивент нужно сделать кучу всякой ерунды и в итоге отправить письмо. Тест триггерит ивент, в конце проверяет, что письмо свалилось в почтовый ящик, а между этими двумя событиями смотрит до куда мы по цепочке дошли. Если тест падает, то падает он не на том, что "письмо за 10 минут так и не свалилось в ящик, фейл" — и иди дальше ищи на чём оно там упало, а более конкретно — "ждали, что вот этот сервис, 1 из 10, сделает вот такую штуку, но он её так и не сделал за 10 минут" — и сразу более-менее понятно где искать.
R>>Но ответа на вопрос как по-быренькому, без поднятия реальной базы, протестировать что все SQL-запросы хотя бы синтаксически валидны я в статье (ожидаемо) не увидел ·>Да так же, как и я говорил: "For databases, access a real database". Т.е. тестами репо+бд, которые он назвал Narrow Integration Tests.
Ну и тогда к чему всё это вступление "е2е тесты — медленные, поэтому читайте километровую статью"?
Здравствуйте, rosencrantz, Вы писали:
R>>>Понравилось про тестирование через structured logging — я так е2е тесты писал для цепочки из нескольких микросервисов. Сервисы пишут structured логи в ELK, а тест запускает транзакцию, и потом мониторит логи по поводу происходящего внутри этой транзакции. В логах пишется когда дошли до какой-то фазы, и релевантные ID, которые тест оттуда вытягивает — и проверяет что там нужные штуки создались, перешли в правильное состояние, и т.д. R>·>По-моему такое годится только для закрытия дыр в операционном дизайне системы. R>А как было бы правильно?
Может я контекст не понял? Признаюсь, статью прочитал далеко не всю.
R>Вся система построена более менее вокруг одно типа транзакций — из внешней системы приходит ивент, в ответ на ивент нужно сделать кучу всякой ерунды и в итоге отправить письмо. Тест триггерит ивент, в конце проверяет, что письмо свалилось в почтовый ящик, а между этими двумя событиями смотрит до куда мы по цепочке дошли. Если тест падает, то падает он не на том, что "письмо за 10 минут так и не свалилось в ящик, фейл" — и иди дальше ищи на чём оно там упало, а более конкретно — "ждали, что вот этот сервис, 1 из 10, сделает вот такую штуку, но он её так и не сделал за 10 минут" — и сразу более-менее понятно где искать.
В смысле автоматизация поиска места где именно свалился тест? Для этого логи мне кажется не самый лучший инструмент. Логи в первую очередь — для человеков, поиска проблем в прошлом.
Сервис X может залогировать что "сообщение успешно отработано", а на самом деле результат положен в очередь, которая не смогла авторизоваться и результат потерялся. Или результат отправлен не в ту очередь, которую слушают правильные получатели.
Как естественная альтернатива логам — собственно прослушка самого трафика между сервисами. Обычно сервисы системы между собой общаются через какой-нибудь mq (это самый гибкий вариант). Тесты могут подписываться на такие топики и подслушивать, как контрольные точки.
R>>>Но ответа на вопрос как по-быренькому, без поднятия реальной базы, протестировать что все SQL-запросы хотя бы синтаксически валидны я в статье (ожидаемо) не увидел R>·>Да так же, как и я говорил: "For databases, access a real database". Т.е. тестами репо+бд, которые он назвал Narrow Integration Tests. R>Ну и тогда к чему всё это вступление "е2е тесты — медленные, поэтому читайте километровую статью"?
Т.е.? Для проверки синтаксиса sql-запросов нужно использовать не e2e-тесты, а narrow IT, т.е. репо+бд. Иными словами, не нужно запускать браузер и клацать кнопки селениумом для проверки чего делает sql.
Если у тебя свалился e2e из-за невалидного sql, то это значит у тебя дыра в нижележащих тестах.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Буравчик, Вы писали:
Б>Думаю, будет интересно ознакомиться, особенно участникам обсуждения тестирования в соседнем топике — каждая из сторон увидит в статье элементы своего подхода к тестированию
Кстати, вспомнилось. Вот такие тесты тоже надо писать ооочень осторожно:
it("describes phase of moon", () => {
const dateOfFullMoon = new Date("8 Dec 2022"); // a date when the moon was actually full
const description = describeMoonPhase(dateOfFullMoon);
assert.equal(description, "The moon is full on December 8th, 2022.";
});
Вот мы понаписали дохрена туеву хучу тестов для разных аспектов системы, используя различные даты when the moon was actually full. А потом внезапно выяснилось, что в getPercentOccluded есть небольшая ошибочка в вычислениях и фикс этой ошибки уронит кучу нерелевантных тестов, т.к. тестовые даты вдруг поменялись.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай