Testing Without Mocks: A Pattern Language
От: Буравчик Россия  
Дата: 08.03.24 09:52
Оценка: 4 (1)
Встретил интересную статью про тестирование. Статья хорошо структурирована, достаточно понятно доносит заложенные идеи и имеет примеры кода.

Оригинал:
Testing Without Mocks: A Pattern Language

Перевод (частичный):
Тестирование без моков: язык шаблонов. Часть 1 (хабр)

Думаю, будет интересно ознакомиться, особенно участникам обсуждения тестирования в соседнем топике — каждая из сторон увидит в статье элементы своего подхода к тестированию
Сразу добавлю, что в статье моки все же есть — в виде так называемых "Nullables"
Best regards, Буравчик
Re: Testing Without Mocks: A Pattern Language
От: · Великобритания  
Дата: 08.03.24 14:25
Оценка: +1
Здравствуйте, Буравчик, Вы писали:

Б>Встретил интересную статью про тестирование. Статья хорошо структурирована, достаточно понятно доносит заложенные идеи и имеет примеры кода.

Я не понял в чём новшество и чем это отличается от моков. Это какая-то специфическая проблема экосистемы 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 и статической типизации.

... дальше потом почитаю, большая статья.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 08.03.2024 14:26 · . Предыдущая версия .
Re: Testing Without Mocks: A Pattern Language
От: rosencrantz США  
Дата: 08.03.24 22:57
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Встретил интересную статью про тестирование.


Спасибо, согласен, что интересная.

Пролистал по диагонали — мне кажется если бы он поширше это написал, сразу получилась бы книжка, которую можно и купить Понравилось про тестирование через structured logging — я так е2е тесты писал для цепочки из нескольких микросервисов. Сервисы пишут structured логи в ELK, а тест запускает транзакцию, и потом мониторит логи по поводу происходящего внутри этой транзакции. В логах пишется когда дошли до какой-то фазы, и релевантные ID, которые тест оттуда вытягивает — и проверяет что там нужные штуки создались, перешли в правильное состояние, и т.д.

Но ответа на вопрос как по-быренькому, без поднятия реальной базы, протестировать что все SQL-запросы хотя бы синтаксически валидны я в статье (ожидаемо) не увидел
Re[2]: Testing Without Mocks: A Pattern Language
От: rosencrantz США  
Дата: 08.03.24 23:15
Оценка:
Здравствуйте, ·, Вы писали:

·>Это полный аналог чего какой-нибудь 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(), то ли оно вообще это звуком юзеру говорит — и пофигу, тест не меняется.
Re[3]: Testing Without Mocks: A Pattern Language
От: · Великобритания  
Дата: 09.03.24 14:13
Оценка:
Здравствуйте, 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".
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 09.03.2024 14:15 · . Предыдущая версия .
Re[2]: Testing Without Mocks: A Pattern Language
От: · Великобритания  
Дата: 09.03.24 18:07
Оценка:
Здравствуйте, 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.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[3]: Testing Without Mocks: A Pattern Language
От: rosencrantz США  
Дата: 09.03.24 19:06
Оценка:
Здравствуйте, ·, Вы писали:

·>Здравствуйте, rosencrantz, Вы писали:


R>>Понравилось про тестирование через structured logging — я так е2е тесты писал для цепочки из нескольких микросервисов. Сервисы пишут structured логи в ELK, а тест запускает транзакцию, и потом мониторит логи по поводу происходящего внутри этой транзакции. В логах пишется когда дошли до какой-то фазы, и релевантные ID, которые тест оттуда вытягивает — и проверяет что там нужные штуки создались, перешли в правильное состояние, и т.д.

·>По-моему такое годится только для закрытия дыр в операционном дизайне системы.

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

R>>Но ответа на вопрос как по-быренькому, без поднятия реальной базы, протестировать что все SQL-запросы хотя бы синтаксически валидны я в статье (ожидаемо) не увидел

·>Да так же, как и я говорил: "For databases, access a real database". Т.е. тестами репо+бд, которые он назвал Narrow Integration Tests.

Ну и тогда к чему всё это вступление "е2е тесты — медленные, поэтому читайте километровую статью"?
Re[4]: Testing Without Mocks: A Pattern Language
От: · Великобритания  
Дата: 09.03.24 20:01
Оценка:
Здравствуйте, 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, то это значит у тебя дыра в нижележащих тестах.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: Testing Without Mocks: A Pattern Language
От: · Великобритания  
Дата: 11.03.24 12:34
Оценка: 5 (1)
Здравствуйте, Буравчик, Вы писали:

Б>Думаю, будет интересно ознакомиться, особенно участникам обсуждения тестирования в соседнем топике — каждая из сторон увидит в статье элементы своего подхода к тестированию

Кстати, вспомнилось. Вот такие тесты тоже надо писать ооочень осторожно:
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 есть небольшая ошибочка в вычислениях и фикс этой ошибки уронит кучу нерелевантных тестов, т.к. тестовые даты вдруг поменялись.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.