Pull to refresh
8
0
Толмачёв Дмитрий @FiresShadow

Разработчик ПО

Send message
Необходимо добавить новое поле (или изменить тип данных существующего).
В конструктор класса Order необходимо передать дополнительный параметр.
Довольно необычное обоснование что класс перегружен ответственностью…

SRP ни в коем случае не призывает делать в одном классе ровно один метод.

Если бы базовые библиотеки языка C# были написаны в том духе, в котором предлагает автор, например вместо
                var a = DateTime.Now;
                var b = a.AddDays(1);
                b = a.AddMinutes(1);

нужно было бы писать
                var a = new NowService.GetNowTime();
                var b = new AddDaysService().Calculate(a, 1);
                b = new AddMinutesService().Calculate(a, 1);

то представьте сколько было бы сервисов, и как сложно было бы пользоваться языком. В продакшн проекте методов как правило тоже не мало, поэтому если будет 100-500 сервисов, то кодом тоже будет сложно пользоваться.
Другое дело, что при использовании богатой доменной модели тоже нужно с умом распределять методы по классам, а не как попало. Например, кастомер может по-разному валидироваться в разных ситуациях, а добавление в кастомера методов ValidateForA(), ValidateForB(),… ValidateForF() быстро перегрузит кастомера ответственностью; валидность — это не характеристика кастомера, а то, как к нему относится кто-то вовне.
Ну и не стоит забывать, что даже в классической богатой доменной модели есть место для сервисов (судя по словам Эрика Эванса).

С автором статьи не согласен. За перевод спасибо.
А также на то, что речь идёт про программирование и ООП намекает то, что используется термин предметная область (часть реального мира, рассматриваемая в пределах данного контекста) и указан хаб «Анализ и проектирование систем». Предметная область — это описание мира глазами пользователя. С точки зрения ветеринара, корова — это биологическая система с её историей, а с точки зрения мясника — кусок мяса определенного качества и веса. Соответственно, похудевшая корова будет эквивалентна самой себе в предметной области ветеринара, и не будет эквивалентна самой себе в предметной области мясника (извиняюсь за жестокий пример). Анализ и проектирование систем — этап разработки программного обеспечения, начинающийся с понимания предметной области пользователя. Наиболее распространенный способ описания предметной области в рамках программы — ООП. Чтобы быть уверенным (а большинству — просто чтобы допустить, ведь это программисткий сайт), что статья не имеет никакого отношения к ООП, нужно быть телепатом. Я бы на вашем месте добавил пометку о том, что речь не идёт о ООП, во избежание недопонимания.
считайте сколько тестов вам нужно для тестирования конкретного функционала.
Другими словами можно сказать — сколько тестов нужно для тестирования контракта метода (выполнения постусловий при соблюдении предусловий).
Тестирование только особенностей тестируемого метода, без привязки к зависимостям?
Я предлагал использовать вместо зависимости моки внешних сервисов и хранилищ данных, и реальные классы во всех остальных случаях. В случае с Джоном и Петей мы имеем дело с пользовательской историей (т.е. требованием, написанным на языке пользователя) «Замужняя дама должна отклонять просьбу зайти в дом в ночное время от мужчины, не являющегося её мужем». Эта пользовательская история закрепляется тестом. В тесте или при помощи билдера создаётся замужняя дама, или просто в свойство «Муж» передаётся Петя, и потом идёт сама проверка. Допустим, логика проверки на мужа была завязана на isEqual с Петей. В какой-то момент было решено, что в программе представляет интерес только, допустим, надёжность или репутация человека, и два человека с одинаковыми уровнями надёжности можно взаимозаменить в любом месте программы, и isEqual переписали. А про логику замужних дам забыли. Если использовать в качестве isEqual реальную реализацию (или функционал сравнения), то тест может отловить ошибку. А если в тесте использовать не Петю и Джона, а Петю и Васю, то по каким-то причинам может и не отловить. Это тоже ещё одна проблема, что тесты не гарантируют 100% надёжности, поскольку тестируются какие-то отдельные случаи, но не все возможные.

Если повсеместно использовать DI то этой проблемы не будет и не будет разницы какие тесты вы пишете, интеграционные или модульные.
Я примерно о том же самом в статье писал.
Любое изменение в коде означает изменение десятка, а то и сотни тестов.
Ответ на этот вопрос есть в статье. Я приводил ссылку на test builder. Нужно будет изменить builder, а не сотни тестов.

Соответственно для каждой ветки развития в функции А есть по 3 ветки развития из функции Б.
3 * 3
Этот вопрос разбирался в статье: «Согласно TDD, тесты предназначены для проверки функционала (feature), а не путей исполнения программы.». Не считайте пути исполнения программы (или ветки развития, как вы их называете), считайте сколько тестов вам нужно для тестирования конкретного функционала.

к тестированию не имеют отношения, к тестированию
Изначально было так:
Я (в статье): Высказывание 2. Интеграционные тесты в меньшей степени помогают в проектировании, нежели модульные…
Вы: Это вообще не относится к проектированию и тестированию. Просто везде используется DI и все.
Я: Если DI и IOC не имеют никакого отношения к проектированию, то я — испанский лётчик.
Вы: к тестированию не имеют отношения, к тестированию

Что-то я ничего не понял. По-вашему, тесты к тестированию не имеют отношения? Что вы вообще хотите спросить\сказать?
Если что, речь шла про тесты в рамках TDD.
А вам самому не кажется странным, что тест то модульный, то сразу интеграционный и это постоянно меняется, причём при изменении не теста, а реализации?
Нет, не кажется. Если тип теста (интеграционный \ модульный) зависит от реализации того, что он тестирует (по определению теста) и от реализации самого теста (ухода от реализации «лишних» классов в тесте за счёт мокирования), то вполне логично, что при изменении одной из реализаций или сразу двух тип теста может измениться.

И я уже задавал вопрос — почему при использовании стандартных классов он остаётся модульным, а при нестандартных перестаёт таковым быть?
На этот вопрос я уже отвечал. Если результат теста зависит от реализации нескольких модулей исходного кода программы, то тест интеграционный (по определению). Исходный код — текст программы на языке программирования. Язык программирования и исходный код программы — это не одно и тоже. Стандартные классы (int,bool,string,DateTime и пр.) — это часть языка, а не модули исходного кода программы.

Вообще, по поводу некоторых терминов можно долго и безрезультатно спорить. Особенно в молодых или неточных науках, где терминология не до конца устоялась и со временем претерпевает изменения, и один и тот же термин может означать немного разные явления в разных кругах. Не исключено, что до появления ООП под интеграционным тестированием понималось нечто другое. Также не исключено, что некоторые люди до сих пор понимают под интеграционным тестированием тоже самое, что понимали до появления ООП. Что под интеграционным тестированием понимаю лично я и те программисты, с которыми я общался, я вам рассказал. И, судя по этой статье, некоторые заокеанские коллеги понимают под интеграционным тестированием тоже самое. Можно конечно в начало статьи поместить длиннющий список определений, что подразумевается под мокой, мокированием, тестом, функционалом, рабочим процессом и прочим. Но что под этим подразумевается и так ясно из контекста. Статьи по программированию — это, как правило, не строгое научное доказательство программистких теорем в виде кванторов, а передача опыта и идей. Я бы не хотел читать статьи, в которых половина текста посвящена определениям исходного кода, рабочего процесса и прочего.

В статье было сказано, что понимается под интеграционным тестом (I use the term integrated test to mean any test whose result (pass or fail) depends on the correctness of the implementation of more than one piece of non-trivial behavior.) и шли рассуждения, почему интеграционных тестов не надо бояться. По сути статьи вопросы есть?
Есть формальные определения. Все подумали и согласись, что вот это будем называть так-то. Если вам определения не нравятся, то это, скорее всего, ваша проблема, а не тех миллионов или миллиардов людей, которые этим определением успешно пользуются.

[Ирония on]Недавно прочитал на микроволновке «не помещать внутрь домашних животных», а у меня на балконе карликовый жираф живёт. Балкон — хоть и часть дома, но такая незначительная, прям вообще не часть дома. Я там кроме рассады и жирафа ничего не держу. Какая же это часть дома?! Это часть природы! Да и жираф — милый такой, совсем не животное. Помыл я жирафа, и решил посушить его в микроволновке, а она возьми и сломайся! Но в гарантийном ремонте мне отказали. Не хотят соглашаться, что жираф с балкона — не домашнее животное. Ну просто глупцы какие-то! [Ирония off]

Надеюсь вас история повеселила.
P.S. История придуманная. Зоозащитники, узбагойтесь.
Например, если меняется внутренняя реализация метода isEqual возвращающая в некоторых случаях false там где возвращала true, то у нас поломается только 1 юнит-тест и нужно будет дополнить его в связи с изменениями. В случае же интеграционного тестирования у нас может половина тестов покраснеть, хотя реально сделанные изменения ничего не ломают и ни как не влияют на корректность результата.

Есть у вас в программе Петя и John, и функция isEqual, которая их различает. И вот в один прекрасный день вы изменяете isEqual и тест на неё, и теперь она их не различает. А ещё есть тест на то, что жена Пети не пускает в три часа ночи незнакомых пьяных мужчин. И вот после изменения isEqual этот тест краснеет. Вы ругаете интеграционное тестирование, и удаляете этот тест, ведь «сделанные изменения ничего не ломают и ни как не влияют на корректность результата». Примерно через год после этого приходит Петя и слёзно спрашивает вас, почему его жена пускает по ночам пьяных незнакомых мужчин, и почему в его семье родился негр, хотя ни у него в роду, ни у его жены негров отродясь не было.

Это вообще не относится к проектированию и тестированию. Просто везде используется DI и все.
Если DI и IOC не имеют никакого отношения к проектированию, то я — испанский лётчик.

Ведь мы же используем реальные зависимости, а не моки, соответственно мы должны протестировать не только функциональность тестируемого метода, но и функциональность используемых зависимостей (в этом же весь смысл).
Прочитайте определение интеграционного теста… Там не сказано, что целью интеграционного теста является тестирование нескольких модулей. Там сказано, что результат прохождения (зелёный или красный) зависит от корректности реализации нескольких модулей (или что модули тестируются в группе). Это большая разница. Определение диктует то, как интеграционный тест должен быть реализован, но не то, с какой целью вы будете его реализовывать, и как вы будете его использовать. Цель определяет TDD. Интеграционные и модульные тесты в плане цели в TDD не различаются — это тестирование одной функциональности. Допустим есть функционал А, Б, В. Каждый функционал реализован в отдельном классе. Код, реализующий функционал А, использует функционал Б. Код, реализующий функционал Б, использует функционал В. Соответственно вы можете написать 6 модульных тестов на А и Б, по 3 на штуку, и точно также можете вместо них написать 6 интеграционных тестов на А и Б. Если код, реализующий функционал В, не использует никакой другой функционал и никакие другие классы, то на функционал В можно написать только модульные тесты. И никто вас не поколотит, если вы после замены моков на реальные классы не напишите ещё 100-500 тестов. Если Б сломается, то интеграционный тест на А упадёт, а модульный — нет (вспоминаем историю с Петей и негром).
Совершенно верно. В одних программах в рамках DDD адрес будет объектом-сущностью (когда нас интересует уникальность жилища; например, когда у каждого жилища есть какой-то рейтинг или история жильцов), а в других — объектом-значением.
из Ваших определений следует, что поделив тестируемый метод пополам, без изменения функционала, мы получим интеграционный тест
Если переместить часть кода тестируемого метода в другие классы, в тестируемом методе вызвать методы этих других классов, и не навесить моки на эти классы, то модульный тест станет интеграционным. Это следует из определений модульного и интеграционного теста на википедии.
Если вспомнить, что определение Фаулера не содекржит упоминаний, что использование «стандартных» классов нещитово
Зато это следует из определений модульного теста и исходного кода программы на википедии. (исходный код != (не равно) язык программирования. исходный код — то, что написано на языке программирования)

Извините, мне не интересно с вами общаться. До свидания.
В классической книге по DDD Эрика Эванса «Предметно-ориентированное программирование» приводится тот же самый пример про человека и адрес, а потом идёт раздел про оптимизацию, и там говорится, что объекты-значения легко подвергнуть денормализации и вставить внутрь таблицы объекта-сущности. Ни слова про то, что поступать так нужно всегда, там нет. Такой совет противоречит DDD. Имхо, вольный пересказ книги у автора статьи не удался.
Имхо, проблема не в примере, а в правиле, которое этот пример иллюстрирует:
Объекты-значения не должны иметь собственной таблицы в БД.
В вопросе объектов-значений и сущностей важное значение имеет следующее правило: всегда предпочитайте объекты-значения сущностям.
Это противоречит самому духу DDD. DDD призывает строить дизайн приложения исходя из предметной области. Т.е. выбор Entity vs Value Object делается исходя из уникальности\неуникальности.
У вас прям сборник вредных советов какой-то получился.
В DDD Aggregation Root для этого есть. Но, автор, видимо, о нём не слышал…
Вы в TDD можете хоть кодревью добавить, хоть необходимость завтракать. Только это будет уже не TDD.
А я разве говорил, что получившийся рабочий процесс обязан называться именно TDD? Не приписывайте мне своих фантазий.

Вывод неправильный.

Ладно, побуду занудой. Докажем, что интеграционные тесты используются в классическом TDD. Раз 1)классическое TDD, согласно Фаулеру, призывает использовать в тестах реальные объекты там, где это возможно и 2)в программировании возможна ситуация, что в методе одного класса используется метод другого класса, и нет веских обстоятельств, мешающих использовать реальный объект (пункт 2 доказывается примером, который я не считаю нужным приводить), то из этих двух пунктов следует, что в классическом TDD возможна ситуация тестирования одного модуля, обращающегося к другому. А по определению интеграционного теста, это интеграционный тест. Следовательно, интеграционные тесты используются в классическом TDD. Теперь согласны? Или снова будете говорит что всё неправильно, не поясняя где вы заподозрили противоречие?
Выходит, по-вашему, интеграционные тесты в рамках TDD использовать нельзя? Ну давайте тогда ваше обоснование, почему нельзя.
Это косяк дизайна. TDD эту проблему решать не задуман.
Я об этом в статье и пишу, и предлагаю или TDD-шный цикл разработки изменить, или не мочить всё подряд без проверки задекларированного в моках поведения.

Если все женщины люди
Не если, а безусловно люди. Раз вы о прекрасном поле заговорили, то, я так понимаю, тема применения интеграционных и модульных тестов в классическом TDD исчерпана?
Вообще, не вижу смысла париться над разделением интеграционные тесты — модульные. Актуальнее проблема классическое TDD — мокисткое.

Information

Rating
Does not participate
Location
Россия
Date of birth
Registered
Activity