> Зачем постоянно делать плохо, чтобы потом исправлять?
Что исправлялось? Писать код в aot-compatible манере было рекомендовано задолго до релиза. А ошибки отображаются через language service, плагины для vsc и вебсторма есть.
1. В том, чтобы редьюсер был чистой функцией и можно было хайпаться на тему фп.
2. У одного экшона может быть много редьюсеров. Разные части приложения могут по-разному реагировать на один и тот же экшон, внося разные изменения в разные куски общего стора.
А это компилятор. У языка есть определенная семантика, unsafePErformIO в нее не входит. С точки зрения хаскеля эта ф-я вообще ничего не делает.
> Ну вот мы и пришли к тому с чего начали — IO оказывается отличным несмываемым маркером зависимости результата работы программы от внешних данных.
Так результат у программы один и тот же — одно и то же ИО. А вот результат ИО — уже другое дело. Но к программе на хаскеле этот результат отношения не имеет :)
А это и не хаскель, unsafePerformIO ломает семантику. Это особенность конкретной реализации.
> совместив «чистую» программу с «грязным» результатом выполнения.
Нету в хаскеле никакого грязного результата выполнения. Когда вы в хаскеле возвращаете IO, то никаких сайд-эффектов не происходит, они происходят при запуске ИО, которое уже к хаскелю не относится, изнутри хаскеля запустить ИО невозможно.
> По классификации Фаулера это стаб, а не мок, и такое использование вполне попадает в «классический», а не «мокистский» стиль тестирования.
Он как раз это не разделяет:
Now I'm at the point where I can explore the second dichotomy: that between classical and mockist TDD. The big issue here is when to use a mock (or other double).
The classical TDD style is to use real objects if possible and a double if it's awkward to use the real thing. So a classical TDDer would use a real warehouse and a double for the mail service. The kind of double doesn't really matter that much.
Хаскель, кстати, проблемы не решит, т.к. действия в ИО-монаде как раз нарушают мое примечание из предыдущего поста (они не валидируются). То есть, вы не можете проверить свое ИО, не запустив его, в итоге для валидации генерящих ИО ф-й вы вынуждены будете мокать зависимости (по вашему подходу с моками нефункциональных зависимостей).
> Если то, что возвращает провайдер, всегда зависит только и исключительно от того, что в него передано, провайдер, как зависимость, можно считать чистой функцией.
Я бы еще добавил, что это «что-то» надо суметь провалидировать внутренними средствами (а то если лямбду возвращать всегда — то у нас тоже получатся везде формально «чистые» функции, но от самой что ни на есть грязной грязи по факту это отличаться не будет, ведь вы не можете проверить свою лямбду, не запустив ее, со всей грязью).
А по примеру — в итоге получается, что мокать вам придется только «запускатор запросов», то есть это, вобщем-то, и есть БД.
> Ограничения архитектуры тоже надо тестировать (до тех пор пока они не гарантируются чем-то, что вам неподконтрольно, конечно).
Есть же вполне себе языковые средства, с-но все ООП как раз про ограничение видимости и изоляции. Естественно, все подобные вещи _можно_ обойти, но если считать, что кто-то это зачем-то будет делать (не имея на то разумной причины) — то тут явно что-то не так. И наличие какой-то зависимости где-то, где она не нужна — будет наименьшей из проблем.
> Ура. Так вот, есть архитектуры, которые удобно и выгодно тестировать с моками.
Да с этим вобщем-то никто и не спорил. Обычно под рекомендацию можно почти всегда построить ситуацию, в которой она неверна (или верна), максим в программировании практически не существует.
> IMetadataProvider.GetMappingFor, очевидно, не чистая зависимость.
Потому что он тянет значение из какого-то хранилища, верно (этого в типе нет, но по смыслу, вроде, ясно)? А если будет так — GetMappingFor возвращает не сам маппинг, а то, как его достать (ну пусть для простоты и конкретики, это просто строка с sql запросом, чисто для примера). Запускать полученные запросы будет какой-то третий сервис, естественно, на него добавиться зависимость (очевидно, нефункциональная) в маппер. Тогда зависимость на провайдер будет функциональной или нет?
Я думаю, в таком ключе смысла обсуждать, что и как тестировать, нет, т.к. архитектор вам также скажет и моки, или не моки. Безусловно, что выбор оптимальной методологии тестирования будет зависеть от архитектуры, это факт самоочевидный. Если архитектура фиксируется неким «архитектором», то это и на способ тестирования автоматически наложит ограничения.
> Вот только надо протестировать, что SUT использует обертку, а не провайдер, да же?
SUT не сможет использовать провайдер, если не содержит зависимостей от провайдера :)
> Ну то есть было void Do(x arg), стало void Do() и x
Ну не обязательно void, может и что-то другое возвращать.
> Первый и самый важный вопрос — а какого типа arg?
Вот я это и хочу узнать, при аргументах с какими свойствами (тип, то как мы работаем со значением внутри метода, еще какие особенности) зависимость будет функциональной, а при какой — не будет. Просто уточниться, чтобы друг друга верно понимать.
> Нет, согласно спецификации этот метод использует маппинг, предоставленный провайдером
Я не могу понять, откуда у вас спецификация знает про провайдер? Почему в спецификацию протекают детали реализации метода? Может быть, я вообще не буду использовать в этом методе указанный провайдер? Собственно, это и есть одна из основных претензий к мокам — они сильно завязаны на реализацию и заставляют часто переписывать тесты (когда реализация поменялась, а спецификация — нет).
> Но, еще раз обращу ваше внимание: в итоге от «не надо мокать зависимости» вы перешли к «давайте делать как можно меньше зависимостей в том коде, который нуждается в обширном тестировании».
Ну так вы тоже перешли от «надо мокать все зависимости» к «функциональные можно и не мокать» :)
> Там не было вопроса (кроме риторического), там был пойнт, что эти кейсы тоже надо тестировать (и они снова требуют знания indirect inputs).
Так если обработать ошибку и выкинуть нужное исключение — это ответственность обертки (или и вовсе провайдера, почему нет? он же с хранилищем взаимодействует), то почему надо делать эти тесты на Map? Тестируйте обертку.
Что исправлялось? Писать код в aot-compatible манере было рекомендовано задолго до релиза. А ошибки отображаются через language service, плагины для vsc и вебсторма есть.
1. В том, чтобы редьюсер был чистой функцией и можно было хайпаться на тему фп.
2. У одного экшона может быть много редьюсеров. Разные части приложения могут по-разному реагировать на один и тот же экшон, внося разные изменения в разные куски общего стора.
А это компилятор. У языка есть определенная семантика, unsafePErformIO в нее не входит. С точки зрения хаскеля эта ф-я вообще ничего не делает.
> Ну вот мы и пришли к тому с чего начали — IO оказывается отличным несмываемым маркером зависимости результата работы программы от внешних данных.
Так результат у программы один и тот же — одно и то же ИО. А вот результат ИО — уже другое дело. Но к программе на хаскеле этот результат отношения не имеет :)
А это и не хаскель, unsafePerformIO ломает семантику. Это особенность конкретной реализации.
> совместив «чистую» программу с «грязным» результатом выполнения.
Нету в хаскеле никакого грязного результата выполнения. Когда вы в хаскеле возвращаете IO, то никаких сайд-эффектов не происходит, они происходят при запуске ИО, которое уже к хаскелю не относится, изнутри хаскеля запустить ИО невозможно.
Он как раз это не разделяет:
Now I'm at the point where I can explore the second dichotomy: that between classical and mockist TDD. The big issue here is when to use a mock (or other double).
The classical TDD style is to use real objects if possible and a double if it's awkward to use the real thing. So a classical TDDer would use a real warehouse and a double for the mail service. The kind of double doesn't really matter that much.
So as we see, state versus behavior verification is mostly not a big decision. The real issue is between classic and mockist TDD.
Когда нет ошибок (работа программы соответствует функциональным требованиям).
Так это же плохо, если тесты падают, когда не надо?
Вроде, причина была разумной, а это предполагает, что должен получиться рабочий код, а не тот, что все сломает.
Хаскель, кстати, проблемы не решит, т.к. действия в ИО-монаде как раз нарушают мое примечание из предыдущего поста (они не валидируются). То есть, вы не можете проверить свое ИО, не запустив его, в итоге для валидации генерящих ИО ф-й вы вынуждены будете мокать зависимости (по вашему подходу с моками нефункциональных зависимостей).
Если разумная причина есть (без кавычек), то это вполне можно сделать.
Я бы еще добавил, что это «что-то» надо суметь провалидировать внутренними средствами (а то если лямбду возвращать всегда — то у нас тоже получатся везде формально «чистые» функции, но от самой что ни на есть грязной грязи по факту это отличаться не будет, ведь вы не можете проверить свою лямбду, не запустив ее, со всей грязью).
А по примеру — в итоге получается, что мокать вам придется только «запускатор запросов», то есть это, вобщем-то, и есть БД.
Есть же вполне себе языковые средства, с-но все ООП как раз про ограничение видимости и изоляции. Естественно, все подобные вещи _можно_ обойти, но если считать, что кто-то это зачем-то будет делать (не имея на то разумной причины) — то тут явно что-то не так. И наличие какой-то зависимости где-то, где она не нужна — будет наименьшей из проблем.
Да с этим вобщем-то никто и не спорил. Обычно под рекомендацию можно почти всегда построить ситуацию, в которой она неверна (или верна), максим в программировании практически не существует.
Это тестировать не надо. Это ограничение архитектуры :)
Потому что он тянет значение из какого-то хранилища, верно (этого в типе нет, но по смыслу, вроде, ясно)? А если будет так — GetMappingFor возвращает не сам маппинг, а то, как его достать (ну пусть для простоты и конкретики, это просто строка с sql запросом, чисто для примера). Запускать полученные запросы будет какой-то третий сервис, естественно, на него добавиться зависимость (очевидно, нефункциональная) в маппер. Тогда зависимость на провайдер будет функциональной или нет?
Я думаю, в таком ключе смысла обсуждать, что и как тестировать, нет, т.к. архитектор вам также скажет и моки, или не моки. Безусловно, что выбор оптимальной методологии тестирования будет зависеть от архитектуры, это факт самоочевидный. Если архитектура фиксируется неким «архитектором», то это и на способ тестирования автоматически наложит ограничения.
> Вот только надо протестировать, что SUT использует обертку, а не провайдер, да же?
SUT не сможет использовать провайдер, если не содержит зависимостей от провайдера :)
Ну не обязательно void, может и что-то другое возвращать.
> Первый и самый важный вопрос — а какого типа arg?
Вот я это и хочу узнать, при аргументах с какими свойствами (тип, то как мы работаем со значением внутри метода, еще какие особенности) зависимость будет функциональной, а при какой — не будет. Просто уточниться, чтобы друг друга верно понимать.
Я не могу понять, откуда у вас спецификация знает про провайдер? Почему в спецификацию протекают детали реализации метода? Может быть, я вообще не буду использовать в этом методе указанный провайдер? Собственно, это и есть одна из основных претензий к мокам — они сильно завязаны на реализацию и заставляют часто переписывать тесты (когда реализация поменялась, а спецификация — нет).
> Но, еще раз обращу ваше внимание: в итоге от «не надо мокать зависимости» вы перешли к «давайте делать как можно меньше зависимостей в том коде, который нуждается в обширном тестировании».
Ну так вы тоже перешли от «надо мокать все зависимости» к «функциональные можно и не мокать» :)
> Там не было вопроса (кроме риторического), там был пойнт, что эти кейсы тоже надо тестировать (и они снова требуют знания indirect inputs).
Так если обработать ошибку и выкинуть нужное исключение — это ответственность обертки (или и вовсе провайдера, почему нет? он же с хранилищем взаимодействует), то почему надо делать эти тесты на Map? Тестируйте обертку.