Полностью поддерживаю! Тоже никогда не понимал этой страсти к физической доске со стикерами. Даже если предположить, что доска нужного размера всегда под рукой (а это не обязательно так), развернуть трекер в гитхабе, гитаре, трелло, да хоть в банальном Экселе ничуть не дольше. Зато возможности по редактированию, детализации, анализу, экспорту/импорту, дистанционной работе и т.д. и т.п. несопоставимо богаче.
С потоком все немного иначе — он Disposable, и по-хорошему закрывать его должен тот, кто создал. Если закрыть поток прямо в нашем методе и потом вернуть его — получится ерунда, потому что никто не сможет его прочитать. Если получить его на входе и его же и вернуть — получается избыточность, и вызывающей стороне будет неочевидно, какой поток использовать — исходный или тот, что пришел из метода (хотя это один и тот же поток). Если открывать файловый поток в методе и возвращать его не закрывая, то по сути теряются все преимущества использования абстракции потока — наш код внезапно наделяет ответственностью принимать решение, что делать, если файл существует, теряется возможность дописать в существующий файл(ну или наоборот, переписывания существующего) без доп. ухищрений в виде перегрузок методов или доп. аргументов.
Неудачная калька с английского. Лучше было бы "если вы не понимаете, о идет речь" или что-то в это роде.
должны не только экспортировать в файл, но и уметь писать в файл.
Что-то тут не так.
public static FileInfo SaveToFile(this IExportService self, Model model, string filePath)
{
using (var output = File.Create(filePath))
{
self.Save(model, output);
return new FileInfo(filePath);
}
}
Лучше так не делать. Если в коде будет вызываться этот метод, замокать его будет практически нереально — да, реализацию IExportService можно будет подменить, чтобы в файл ничего не писалось, но сам файл на диске будет создаваться, со всеми вытекающими недостатками.
Тогда я прошу его рассказать, за что он хочет такие деньги, что он умеет? В итоге понимаю, что знаю гораздо больше, но работаю за меньшие деньги.
Вроде и грамотно пишете, но вот такой способ оценивания — путём сравнения с собой и своей зарплатой — обычно характерен для новичков. Скажем, я (бек-энд разработчик) задам кандидату какой-нибудь вопрос про ConcurrentDictionary, а он не ответит — и я такой "ммм, а я-то это знаю, значит, стою больше". А он может быть знает кучу других вещей, про которые я даже не подумал спросить, потому что сам с ними не работал в жизни. И может статься, что эти его знания позволят радикально улучшить проект?
В этом смысле грейды по принципу "джун должен знать X, мидл — X и Y, а сеньор — еще и Z впридачу" — действительно не имеют смысла. А если система грейдов отражает степень зрелости, готовности к самостоятельной работе и принятию решений — то она никому и не мешает и проблем особо не несет.
Вот тут вступают интеграционных тесты, при этом, если они построены на надежность фундаменте из модульных тестов, их нужно сильно меньше по количеству, им не нужно проверять все допустимые комбинации входных параметров — в идеале (слабодостижимом, правда) может хватить и одного интеграционного теста, который проверит happy path, полагаясь на то, что детали функционирования всех компонентов уже проверены.
Проводя аналогию — перед сборкой агрегата мы полагается на то, что каждая деталь изготовлена с соблюдением допусков (это проверил нормоконтроль), и если он успешно запустился, то в целом, скорее всего, работает. А вот на тестовом запуске обнаружить, что 50% гаек бракованные, и на болтах не держатся — не самая эффективная стратегия, хотя, бесспорно, тестовый запуск это тоже выявит — движок заклинило, тест красный.
Ах, ну и да, в чистом e2e я вынужден был бы создавать десятки договоров прямого страхования, подбирая параметры так, чтобы получить нужные страховые суммы, и при малейших изменениях в модуле учета договоров (добавлен новый обязательный параметр) они бы переставали работать, хотя в перестраховании не поменялось вообще ничего. При тестах на моках я просто заставлял репозитории возвращать данные, необходимые для теста.
На простых примерах это работает. В сложной системе может получиться ситуация, когда вызов такого корневого метода задействует несколько десятков других сущностей, и вместо ожидаемого результата 64578.65 мы получаем 123577.22 — и вот совершенно непонятно, на каком этапе вычислений получена ошибка. При хорошем уровне покрытия этих связанных сущностей модульными тестами, большая их часть будут зелеными, и исправить надо красные, после этого, если все ОК, то и e2e зазеленеют.
Из наиболее эпичных примеров, где плотное использование модульных тестов было настоящим life saver -ом — у меня был расчет перестраховочной премии. На входе всего три параметра — даты начала и окончания периода, да номер договора перестрахования, а на выходе — всего одно число. Зато неявных параметров — вагон и маленькая тележка. Поэтому отдельно проверяется логика (несамая тривиальная) отбора действующих договоров прямого страхования за период, отдельно — учет кумуляции в рамках застрахованного лица (там тоже кхм, интересные выверты в требованиях были), отдельно — логика преобразования валют (с учетом, что произвольный курс может быть зашит в договор перестрахования — а может не быть, и тогда надо брать курс ЦБ), отдельно — отнесение прямых договоров к тому или иному лееру договора перестрахования, отдельно — логика отбора бордеро предыдущих периодов, которые тоже участвуют в кумуляции и т.д.и т. п. E2e тесты просто непрерывно были бы красными, не давая никакой информации, на каком же этапе возникла ошибка. (Что не умаляет их нужности; они тоже нужны, но их одних тоже недостаточно).
При изменении бизнес-требований надо менять бизнес-логику И тесты. Ваш КО.
Но это вообще никак не противоречит тому, что я писал выше, и справедливо для многих видов тестов, не только модульных. Если в e2e тесте проверяется расчет суммы заказа, а с даты X изменилась, допустим, ставка НДС, то старый тест так же придется менять.
Это неверно, на множестве входных параметров есть классы эквивалентности, нужно их проверить.
Нет, речь идет именно про 4 класса эквивалентности входных параметров для каждого из методов, которые влияют на то, по какой ветке пойдет выполнение. Условно, для int32 у нас 4 миллиарда варианта входных параметров, но с т.з. конкретного метода могут быть различия для категорий "все отрицательные", "ноль", "положительные до тысячи", "положительные от тысячи и более" — их-то мы и проверяем.
А то, что вы проверили каждый метод в отдельности ничего не значит.
Очень даже значит. Если мы проверили метод и убедились, что он работает корректно на любых входных данных, то проверка вызывающего кода сильно упрощается. Но я нигде и не говорил, что юнит-тестов достаточно, и что проверив все ветви выполнения можно остановиться.
Юнит-тесты, конечно, не панацея, но они имеют два важных преимущества, которых не дают другие виды тестов:
простота тестирования граничных случаев. Иногда, чтобы протестировать поведение системы в каком-то очень специфичном случае может потребовать тонкая подгонка входных параметров — зачастую очень нетривиальная, требующая знания деталей реализации каждого из звеньев цепочки вызовов. В случае юнит-теста мы просто передаем граничные параметры, на которых хотим проверить работу определенного метода, в этот метод.
устранение комбинаторного взрыва. Представим модельный пример, в котором есть 4 метода, вызывающих друг друга, в каждом из которых выполнение может идти по 4 разным путям. В случае, если мы захотим качественно покрыть e2e тестами все пути выполнения, нам придется подобрать 44=256 различных сочетаний входных параметров, что может оказаться весьма непростой задачей, и очевидно, выполнено в полном объеме не будет — разработчик покроет тестами наиболее вероятные сценарии, понадеявшись, что и остальные работают корректно. В случае юнит тестов, нам может хватить 4*4=16 тестов, чтобы проверить все ветви исполнения кода — и это уже выполнимая задача. Это, конечно, все еще не гарантирует, что система работает корректно (одних юнит-тестов недостаточно), но сильно повышает нашу уверенность в ней.
Не-не-не, это объяснило бы, почему для микросервисов график растет медленнее, чем для монолита, но не почему он падает. Почему в монолите написать сотую тысячу строк может быть вдесятеро сложнее, чем пятидесятую тысячу — понятно. Но почему в микросервисе написание десятой тысячи строк оказывается вдесятеро легче, чем написание пятой тысячи — совсем неочевидно, тут я солидарен с le1ic
А кроме того, вряд ли кто-то ответит на этот вопрос что-то принципиально отличное от "Увольняем по основаниям, предусмотренных ТК РФ", даже если это не вся правда — потому что кому охота давать чистосердечное. Так что вопрос не только вредный, но и бесполезный.
Не знаю, меня как интервьюера очень насторожил бы вопрос "за что у вас увольняют". Адекватных сотрудников не то что не увольняют, а наоборот, всячески удерживают, и у них такой вопрос не возникает. А вот раз для соискателя этот вопрос принципиально важен, настолько важен, что он его задает на собеседовании — это дает определенную пищу для размышлений, почему этот вопрос для него имеет такое значение, не было ли у него увольнений за серьезные косяки, о которых он умолчал. И учитывая, что для компании обычно безопаснее ошибочно не нанять подходящего кандидата, чем ошибочно нанять неподходящего, то у такого соискателя неиллюзорно вырастают шансы отказа.
Распухание бизнес-логики при усложнении бизнес-требований — это как раз нормально, разве нет? Хуже, если со временем распухание инфраструктурный слой без добавления бизнес-функций, просто чтоб было.
Если по теме — то ваш подход может быть вполне оправдан для проектов, время жизни которых заведомо меньше времени устаревания фреймворка. В иных случаях может оказаться, что бизнес-логику написали в коде windows forms компонентов (потому что зачем усложнять, выделяя BL?), и теперь дешевле все выкинуть и начать сначала. Аналогично, через 10 лет такую же брезгливость будет вызывать бизнес-логика в контроллера, прибитых гвоздями к допотопному ASP. Net Core 3.1.
Не 6, а скорее 10-15… Хотя думаю, люди постарше сказали бы, что слышат о том, насколько перспективно направление ГИС, и как с их помощью можно оптимизировать расположение чего угодно, годов с 80-х.
Но если коллегам действительно удалось показать коммерчески значимый результат — могу за них только порадоваться!
Полностью поддерживаю! Тоже никогда не понимал этой страсти к физической доске со стикерами. Даже если предположить, что доска нужного размера всегда под рукой (а это не обязательно так), развернуть трекер в гитхабе, гитаре, трелло, да хоть в банальном Экселе ничуть не дольше. Зато возможности по редактированию, детализации, анализу, экспорту/импорту, дистанционной работе и т.д. и т.п. несопоставимо богаче.
С потоком все немного иначе — он Disposable, и по-хорошему закрывать его должен тот, кто создал. Если закрыть поток прямо в нашем методе и потом вернуть его — получится ерунда, потому что никто не сможет его прочитать. Если получить его на входе и его же и вернуть — получается избыточность, и вызывающей стороне будет неочевидно, какой поток использовать — исходный или тот, что пришел из метода (хотя это один и тот же поток). Если открывать файловый поток в методе и возвращать его не закрывая, то по сути теряются все преимущества использования абстракции потока — наш код внезапно наделяет ответственностью принимать решение, что делать, если файл существует, теряется возможность дописать в существующий файл(ну или наоборот, переписывания существующего) без доп. ухищрений в виде перегрузок методов или доп. аргументов.
Неудачная калька с английского. Лучше было бы "если вы не понимаете, о идет речь" или что-то в это роде.
Что-то тут не так.
Лучше так не делать. Если в коде будет вызываться этот метод, замокать его будет практически нереально — да, реализацию
IExportService
можно будет подменить, чтобы в файл ничего не писалось, но сам файл на диске будет создаваться, со всеми вытекающими недостатками.Вроде и грамотно пишете, но вот такой способ оценивания — путём сравнения с собой и своей зарплатой — обычно характерен для новичков. Скажем, я (бек-энд разработчик) задам кандидату какой-нибудь вопрос про ConcurrentDictionary, а он не ответит — и я такой "ммм, а я-то это знаю, значит, стою больше". А он может быть знает кучу других вещей, про которые я даже не подумал спросить, потому что сам с ними не работал в жизни. И может статься, что эти его знания позволят радикально улучшить проект?
В этом смысле грейды по принципу "джун должен знать X, мидл — X и Y, а сеньор — еще и Z впридачу" — действительно не имеют смысла. А если система грейдов отражает степень зрелости, готовности к самостоятельной работе и принятию решений — то она никому и не мешает и проблем особо не несет.
Ооо, я, кажется, вернулся в 2003-й — давненько мне не попадались холивары на тему Far vs WC/TC!
При этом автор притянул TotalCommander в статью за уши, выбрав коммерческий продукт для иллюстрации топорности UI опенсорсных решений.
Выглядит, как переизобретенная концепция Middleware… Или я что-то упустил?
Вот тут вступают интеграционных тесты, при этом, если они построены на надежность фундаменте из модульных тестов, их нужно сильно меньше по количеству, им не нужно проверять все допустимые комбинации входных параметров — в идеале (слабодостижимом, правда) может хватить и одного интеграционного теста, который проверит happy path, полагаясь на то, что детали функционирования всех компонентов уже проверены.
Проводя аналогию — перед сборкой агрегата мы полагается на то, что каждая деталь изготовлена с соблюдением допусков (это проверил нормоконтроль), и если он успешно запустился, то в целом, скорее всего, работает. А вот на тестовом запуске обнаружить, что 50% гаек бракованные, и на болтах не держатся — не самая эффективная стратегия, хотя, бесспорно, тестовый запуск это тоже выявит — движок заклинило, тест красный.
Ах, ну и да, в чистом e2e я вынужден был бы создавать десятки договоров прямого страхования, подбирая параметры так, чтобы получить нужные страховые суммы, и при малейших изменениях в модуле учета договоров (добавлен новый обязательный параметр) они бы переставали работать, хотя в перестраховании не поменялось вообще ничего. При тестах на моках я просто заставлял репозитории возвращать данные, необходимые для теста.
На простых примерах это работает. В сложной системе может получиться ситуация, когда вызов такого корневого метода задействует несколько десятков других сущностей, и вместо ожидаемого результата 64578.65 мы получаем 123577.22 — и вот совершенно непонятно, на каком этапе вычислений получена ошибка. При хорошем уровне покрытия этих связанных сущностей модульными тестами, большая их часть будут зелеными, и исправить надо красные, после этого, если все ОК, то и e2e зазеленеют.
Из наиболее эпичных примеров, где плотное использование модульных тестов было настоящим life saver -ом — у меня был расчет перестраховочной премии. На входе всего три параметра — даты начала и окончания периода, да номер договора перестрахования, а на выходе — всего одно число. Зато неявных параметров — вагон и маленькая тележка. Поэтому отдельно проверяется логика (несамая тривиальная) отбора действующих договоров прямого страхования за период, отдельно — учет кумуляции в рамках застрахованного лица (там тоже кхм, интересные выверты в требованиях были), отдельно — логика преобразования валют (с учетом, что произвольный курс может быть зашит в договор перестрахования — а может не быть, и тогда надо брать курс ЦБ), отдельно — отнесение прямых договоров к тому или иному лееру договора перестрахования, отдельно — логика отбора бордеро предыдущих периодов, которые тоже участвуют в кумуляции и т.д.и т. п. E2e тесты просто непрерывно были бы красными, не давая никакой информации, на каком же этапе возникла ошибка. (Что не умаляет их нужности; они тоже нужны, но их одних тоже недостаточно).
Я сожалею, но я окончательно перестал понимать предмет спора, поэтому заканчиваю дискуссию.
При изменении бизнес-требований надо менять бизнес-логику И тесты. Ваш КО.
Но это вообще никак не противоречит тому, что я писал выше, и справедливо для многих видов тестов, не только модульных. Если в e2e тесте проверяется расчет суммы заказа, а с даты X изменилась, допустим, ставка НДС, то старый тест так же придется менять.
Нет, речь идет именно про 4 класса эквивалентности входных параметров для каждого из методов, которые влияют на то, по какой ветке пойдет выполнение. Условно, для int32 у нас 4 миллиарда варианта входных параметров, но с т.з. конкретного метода могут быть различия для категорий "все отрицательные", "ноль", "положительные до тысячи", "положительные от тысячи и более" — их-то мы и проверяем.
Очень даже значит. Если мы проверили метод и убедились, что он работает корректно на любых входных данных, то проверка вызывающего кода сильно упрощается. Но я нигде и не говорил, что юнит-тестов достаточно, и что проверив все ветви выполнения можно остановиться.
Юнит-тесты, конечно, не панацея, но они имеют два важных преимущества, которых не дают другие виды тестов:
Не-не-не, это объяснило бы, почему для микросервисов график растет медленнее, чем для монолита, но не почему он падает. Почему в монолите написать сотую тысячу строк может быть вдесятеро сложнее, чем пятидесятую тысячу — понятно. Но почему в микросервисе написание десятой тысячи строк оказывается вдесятеро легче, чем написание пятой тысячи — совсем неочевидно, тут я солидарен с le1ic
А кроме того, вряд ли кто-то ответит на этот вопрос что-то принципиально отличное от "Увольняем по основаниям, предусмотренных ТК РФ", даже если это не вся правда — потому что кому охота давать чистосердечное. Так что вопрос не только вредный, но и бесполезный.
Не знаю, меня как интервьюера очень насторожил бы вопрос "за что у вас увольняют". Адекватных сотрудников не то что не увольняют, а наоборот, всячески удерживают, и у них такой вопрос не возникает. А вот раз для соискателя этот вопрос принципиально важен, настолько важен, что он его задает на собеседовании — это дает определенную пищу для размышлений, почему этот вопрос для него имеет такое значение, не было ли у него увольнений за серьезные косяки, о которых он умолчал. И учитывая, что для компании обычно безопаснее ошибочно не нанять подходящего кандидата, чем ошибочно нанять неподходящего, то у такого соискателя неиллюзорно вырастают шансы отказа.
Копипаста?
Ну нельзя же настолько откровенный гуглоперевод публиковать!
Распухание бизнес-логики при усложнении бизнес-требований — это как раз нормально, разве нет? Хуже, если со временем распухание инфраструктурный слой без добавления бизнес-функций, просто чтоб было.
Если по теме — то ваш подход может быть вполне оправдан для проектов, время жизни которых заведомо меньше времени устаревания фреймворка. В иных случаях может оказаться, что бизнес-логику написали в коде windows forms компонентов (потому что зачем усложнять, выделяя BL?), и теперь дешевле все выкинуть и начать сначала. Аналогично, через 10 лет такую же брезгливость будет вызывать бизнес-логика в контроллера, прибитых гвоздями к допотопному ASP. Net Core 3.1.
Не 6, а скорее 10-15… Хотя думаю, люди постарше сказали бы, что слышат о том, насколько перспективно направление ГИС, и как с их помощью можно оптимизировать расположение чего угодно, годов с 80-х.
Но если коллегам действительно удалось показать коммерчески значимый результат — могу за них только порадоваться!