В настоящее время в нашей команде ведутся дебаты о том, является ли изменение дизайна кода, позволяющее модульное тестирование, запахом кода, или в какой степени это может быть сделано без запаха кода. Это произошло потому, что мы только начинаем внедрять практики, которые присутствуют практически во всех других компаниях, занимающихся разработкой программного обеспечения.
В частности, у нас будет служба Web API, которая будет очень тонкой. Его основной обязанностью будет составление веб-запросов / ответов и вызов базового API, который содержит бизнес-логику.
Одним из примеров является то, что мы планируем создать фабрику, которая будет возвращать тип метода аутентификации. Нам не нужно, чтобы он наследовал интерфейс, так как мы не ожидаем, что он когда-либо будет отличаться от конкретного типа, которым он будет. Однако для модульного тестирования сервиса Web API нам нужно будет смоделировать эту фабрику.
По сути, это означает, что мы либо проектируем класс контроллера Web API для приема DI (через его конструктор или установщик), что означает, что мы разрабатываем часть контроллера просто для того, чтобы разрешить DI и реализовать интерфейс, который нам не нужен в противном случае, или мы используем сторонний фреймворк, такой как Ninject, чтобы избежать необходимости проектировать контроллер таким образом, но нам все равно придется создавать интерфейс.
Некоторые в команде, похоже, не хотят разрабатывать код только для тестирования. Мне кажется, что должен быть какой-то компромисс, если вы надеетесь провести модульное тестирование, но я не уверен, как развеять их опасения.
Просто чтобы прояснить, это совершенно новый проект, так что на самом деле речь не идет об изменении кода для включения модульного тестирования; речь идет о разработке кода, который мы собираемся написать для модульного тестирования.
источник
Ответы:
Нежелание модифицировать код ради тестирования показывает, что разработчик не понимает роль тестов и, как следствие, свою собственную роль в организации.
Бизнес программного обеспечения вращается вокруг предоставления базы кода, которая создает ценность для бизнеса. Благодаря долгому и горькому опыту мы обнаружили, что мы не можем создавать такие кодовые базы нетривиального размера без тестирования. Поэтому тестовые наборы являются неотъемлемой частью бизнеса.
Многие кодеры отдают должное этому принципу, но подсознательно никогда не принимают его. Легко понять, почему это так; осознание того, что наши собственные умственные способности не бесконечны и на самом деле удивительно ограничены, когда сталкиваются с огромной сложностью современной кодовой базы, нежелательно и легко подавляется или рационализируется. Тот факт, что тестовый код не доставляется клиенту, позволяет легко поверить в то, что он является гражданином второго сорта и несущественным по сравнению с «существенным» бизнес-кодом. И идея добавления тестового кода в бизнес-код кажется многим вдвойне оскорбительной.
Проблема с обоснованием этой практики связана с тем, что всю картину того, как создается ценность в программном бизнесе, часто понимают только вышестоящие лица в иерархии компании, но эти люди не имеют детального технического понимания рабочий процесс кодирования, необходимый для понимания, почему от тестирования нельзя избавиться. Поэтому они слишком часто усмиряются практикующими, которые уверяют их, что тестирование может быть хорошей идеей в целом, но «мы являемся элитными программистами, которым такие костыли не нужны», или «у нас нет времени на это прямо сейчас» и т. д. и т. д. Тот факт, что успех в бизнесе - это игра чисел, которая позволяет избежать технического долга, гарантирует качество и т. д. показывает свою ценность только в более долгосрочной перспективе, что означает, что они часто весьма искренни в этом убеждении.
Короче говоря: создание тестируемого кода является неотъемлемой частью процесса разработки, ничем не отличающегося от других областей (многие микрочипы разработаны с существенной долей элементов только для целей тестирования), но очень легко пропустить очень веские причины для это. Не попадитесь в эту ловушку.
источник
Это не так просто, как вы думаете. Давайте разберемся с этим.
НО!
Любое изменение в вашем коде может привести к ошибке. Поэтому изменение кода без уважительной деловой причины не является хорошей идеей.
Ваш «очень тонкий» webapi не кажется лучшим примером для модульного тестирования.
Изменение кода и тестов одновременно - это плохо.
Я бы предложил следующий подход:
Написать интеграционные тесты . Это не должно требовать каких-либо изменений кода. Он даст вам базовые тестовые примеры и позволит вам убедиться, что любые дальнейшие изменения кода, которые вы делаете, не приводят к ошибкам.
Убедитесь, что новый код тестируемый и имеет модульные и интеграционные тесты.
Убедитесь, что ваша цепочка CI выполняет тесты после сборок и развертываний.
Когда вы настроите эти вещи, только тогда начинайте думать о рефакторинге устаревших проектов для тестируемости.
Надеемся, что все извлекут уроки из этого процесса и составят хорошее представление о том, где тестирование наиболее необходимо, как вы хотите его структурировать и какое значение оно приносит для бизнеса.
РЕДАКТИРОВАТЬ : С тех пор как я написал этот ответ, ОП разъяснил вопрос, чтобы показать, что они говорят о новом коде, а не об изменениях существующего кода. Я, возможно, наивно подумал: «Является ли модульное тестирование хорошим?» Аргумент был улажен несколько лет назад.
Трудно представить, какие изменения кода потребуются для модульных тестов, но в любом случае это не будет хорошей практикой. Вероятно, было бы целесообразно изучить реальные возражения, возможно, возражает против стиля модульного тестирования.
источник
Разработка кода для тестирования по сути не является запахом кода; напротив, это признак хорошего дизайна. Существует несколько известных и широко используемых шаблонов проектирования, основанных на этом (например, Model-View-Presenter), которые предлагают простое (более легкое) тестирование как большое преимущество.
Итак, если вам нужно написать интерфейс для вашего конкретного класса, чтобы его было проще протестировать, это хорошо. Если у вас уже есть конкретный класс, большинство IDE могут извлечь из него интерфейс, что потребует минимальных усилий. Это немного больше работы для синхронизации двух, но интерфейс в любом случае не должен сильно меняться, и выгоды от тестирования могут перевесить эти дополнительные усилия.
С другой стороны, как @MatthieuM. упомянуто в комментарии, если вы добавляете в свой код определенные точки входа, которые никогда не должны использоваться в производстве, исключительно для тестирования, это может быть проблемой.
источник
_ForTest
) и проверьте кодовую базу для вызовов из не тестового кода.ИМХО очень просто понять, что для создания модульных тестов тестируемый код должен иметь хотя бы определенные свойства. Например, если код не состоит из отдельных модулей, которые можно тестировать изолированно, слово «модульное тестирование» даже не имеет смысла. Если код не имеет этих свойств, его нужно сначала изменить, это довольно очевидно.
Сказано, что теоретически можно сначала попытаться написать какой-то тестируемый код, применяя все принципы SOLID, а затем попытаться написать для него тест, без дальнейшей модификации исходного кода. К сожалению, написание кода, который действительно тестируется на модуле, не всегда очень просто, поэтому весьма вероятно, что потребуются некоторые изменения, которые будут обнаружены только при попытке создания тестов. Это верно для кода, даже когда он был написан с мыслью о модульном тестировании, и это определенно более верно для кода, который был написан там, где «модульная тестируемость» не была в повестке дня в начале.
Существует хорошо известный подход, который пытается решить проблему, сначала написав модульные тесты - он называется Test Driven Development (TDD), и он, безусловно, может помочь сделать код более модульно тестируемым с самого начала.
Конечно, нежелание впоследствии менять код, чтобы сделать его тестируемым, часто возникает в ситуации, когда код сначала тестировался вручную и / или отлично работал в процессе разработки, поэтому его изменение может фактически привести к появлению новых ошибок, это правда. Лучший подход для смягчения этого - сначала создать набор регрессионных тестов (который часто может быть реализован с минимальными изменениями в кодовой базе), а также другие сопутствующие меры, такие как проверки кода или новые сеансы ручного тестирования. Это должно дать вам достаточно уверенности, чтобы убедиться, что переделка некоторых внутренних устройств не сломает ничего важного.
источник
Я не согласен с (необоснованным) утверждением, которое вы делаете:
Это не обязательно правда. Есть много способов , чтобы писать тесты, и есть способы , чтобы написать юнит - тесты , которые не связаны с издеваются. Что еще более важно, существуют другие виды тестов, такие как функциональные или интеграционные тесты. Много раз можно найти «тестовый шов» на «интерфейсе», который не является языком программирования ООП
interface
.Некоторые вопросы, которые помогут вам найти альтернативный тестовый шов, который может быть более естественным:
Другое необоснованное утверждение, которое вы делаете, касается DI:
Внедрение зависимости не обязательно означает создание нового
interface
. Например, в случае токена аутентификации: вы можете просто создать настоящий токен аутентификации программно? Затем тест может создавать такие токены и вводить их. Зависит ли процесс проверки токена от какого-либо криптографического секрета? Я надеюсь, что вы не жестко закодировали секрет - я ожидаю, что вы можете каким-то образом прочитать его из хранилища, и в этом случае вы можете просто использовать другой (общеизвестный) секрет в ваших тестовых примерах.Это не значит, что вы никогда не должны создавать новое
interface
. Но не зацикливайтесь на том, что существует только один способ написания теста или один способ имитации поведения. Если вы мыслите нестандартно, вы обычно можете найти решение, которое потребует минимум искажений вашего кода и все же даст вам желаемый эффект.источник
Вам повезло, так как это новый проект. Я обнаружил, что Test Driven Design очень хорошо работает для написания хорошего кода (именно поэтому мы делаем это в первую очередь).
По выяснить , впереди , как вызвать данный кусок кода с реалистичными входными данными, а затем получить реалистичные выходные данные , которые можно проверить, как задумано, вы делаете дизайн API очень рано в процессе и имеют хорошие шансы на получение полезный дизайн, потому что вам не мешает существующий код, который должен быть переписан для соответствия. Кроме того, ваши коллеги легче понимают это, чтобы вы могли снова начать хорошие дискуссии в начале процесса.
Обратите внимание, что «полезно» в вышеприведенном предложении означает не только то, что результирующие методы легко вызывать, но также то, что вы стремитесь получить чистые интерфейсы, которые легко настроить в интеграционных тестах, и написать макеты.
Учти это. Особенно с экспертной оценкой. По моему опыту, затраты времени и усилий будут очень быстро возвращены.
источник
UserCanChangeTheirPassword
, то в тесте вы вызываете (еще не существующую) функцию для изменения пароля, а затем утверждаете, что пароль действительно изменен. Затем вы пишете функцию до тех пор, пока не сможете запустить тест, и она не выдает ни исключений, ни ошибочных утверждений. Если в этот момент у вас есть причина для добавления какого-либо кода, то эта причина входит в другой тест, напримерUserCantChangePasswordToEmptyString
.CalculateFactorial
который просто возвращает 120 и тест проходит успешно. То есть минимум. Это также явно не то, что было задумано, но это просто означает, что вам нужен еще один тест для выражения того, что было задумано.Если вам нужно изменить код, это запах кода.
Исходя из личного опыта, если мой код трудно писать тесты, это плохой код. Это не плохой код, потому что он не работает и не работает как задумано, это плохо, потому что я не могу быстро понять, почему он работает. Если я сталкиваюсь с ошибкой, я знаю, что это будет долгая мучительная работа, чтобы исправить это. Код также трудно / невозможно использовать повторно.
Хороший (чистый) код разбивает задачи на более мелкие разделы, которые легко понять с первого взгляда (или, по крайней мере, с хорошим видом). Тестирование этих небольших разделов легко. Я также могу написать тесты, которые тестируют только часть кода с такой же легкостью, если я достаточно уверен в подразделах (повторное использование также помогает здесь, так как оно уже было протестировано).
Сделайте код легким для тестирования, простым рефакторингом и простым для повторного использования с самого начала, и вы не будете убивать себя, когда вам нужно будет внести изменения.
Я набираю это, полностью перестраивая проект, который должен был стать однообразным прототипом, в более чистый код. Гораздо лучше понять это с самого начала и как можно скорее изменить плохой код, а не часами смотреть на экран, боясь прикоснуться к чему-либо, опасаясь сломать что-то, что частично работает.
источник
Я бы сказал, что написание кода, который не может быть проверен модулем, является запахом кода. В целом, если ваш код не может быть модульным тестированием, он не является модульным, что затрудняет его понимание, поддержку или улучшение. Возможно, если код представляет собой клейкий код, который действительно имеет смысл только с точки зрения интеграционного тестирования, вы можете заменить интеграционное тестирование на модульное тестирование, но даже тогда, когда интеграция не удалась, вам придется изолировать проблему, и модульное тестирование - отличный способ сделай это.
Ты говоришь
Я не очень следую этому. Причина наличия фабрики, которая создает что-то, состоит в том, чтобы позволить вам изменять фабрики или изменять то, что фабрика создает легко, поэтому другие части кода не нужно менять. Если ваш метод аутентификации никогда не изменится, то на фабрике бесполезный код. Однако, если вы хотите, чтобы в тестировании использовался другой метод аутентификации, чем на производстве, отличным решением будет наличие фабрики, которая возвращает другой метод аутентификации в тесте, чем на производстве.
Вам не нужны DI или Mocks для этого. Вам просто нужно, чтобы ваша фабрика поддерживала различные типы аутентификации и чтобы ее можно было как-то настраивать, например, из файла конфигурации или переменной среды.
источник
В каждой инженерной дисциплине, о которой я могу думать, есть только один способ достичь достойного или более высокого уровня качества:
Для учета осмотра / тестирования в проекте.
Это относится и к строительству, дизайну чипов, разработке программного обеспечения и производству. Теперь, это не значит, что тестирование - это основа, вокруг которой должен строиться каждый дизайн, а не вообще. Но с каждым дизайнерским решением дизайнеры должны четко понимать влияние на затраты на тестирование и принимать осознанные решения о компромиссе.
В некоторых случаях ручное или автоматическое (например, Selenium) тестирование будет более удобным, чем модульные тесты, и в то же время обеспечивает приемлемое тестовое покрытие самостоятельно. В редких случаях также может быть приемлемым выкидывание чего-либо, что практически не проверено. Но это должны быть осознанные индивидуальные решения. Называя дизайн, который учитывает тестирование «запаха кода», указывает на серьезный недостаток опыта.
источник
Я обнаружил, что модульное тестирование (и другие виды автоматического тестирования) имеют тенденцию уменьшать запахи кода, и я не могу вспомнить ни одного примера, когда они вводят запахи кода. Модульные тесты обычно вынуждают вас писать лучший код. Если вы не можете использовать тестируемый метод, почему в вашем коде должно быть проще?
Хорошо написанные модульные тесты показывают, как этот код предназначен для использования. Они являются формой исполняемой документации. Я вижу ужасно написанные, слишком длинные юнит-тесты, которые просто невозможно понять. Не пиши их! Если вам нужно написать длинные тесты для настройки ваших классов, ваши классы нуждаются в рефакторинге.
Модульные тесты покажут, где некоторые запахи вашего кода. Я бы посоветовал прочитать « Эффективную работу Майкла С. Перса с устаревшим кодексом» . Несмотря на то, что ваш проект новый, если в нем еще нет (или много) модульных тестов, вам могут потребоваться некоторые неочевидные приемы, чтобы ваш код хорошо тестировался.
источник
В двух словах:
Тестируемый код - это (обычно) обслуживаемый код, или, скорее, код, который сложно протестировать, обычно сложно поддерживать. Разработка кода, который не подлежит тестированию, похожа на разработку машины, которая не подлежит ремонту - жаль бедного чмока, которому в конечном итоге будет поручено его починить (это может быть вы).
Вы знаете, что вам понадобится пять различных типов методов аутентификации через три года, теперь, когда вы это сказали, верно? Требования меняются, и, хотя вы должны избегать чрезмерной разработки вашего проекта, наличие тестируемого дизайна означает, что ваш дизайн имеет (просто) достаточно швов, которые можно изменить без (слишком большой) боли, и что модульные тесты предоставят вам автоматизированные средства, чтобы увидеть, что ваши изменения ничего не нарушают.
источник
Проектирование на основе внедрения зависимостей не является запахом кода - это лучшая практика. Использование DI не только для тестируемости. Создание ваших компонентов на основе DI способствует модульности и возможности многократного использования, облегчает замену основных компонентов (таких как уровень интерфейса базы данных). Хотя это добавляет определенную степень сложности, все сделано правильно, это позволяет лучше разделять слои и изолировать функциональность, что упрощает управление сложностью и ее навигацию. Это упрощает правильную проверку поведения каждого компонента, уменьшает количество ошибок, а также позволяет легче отслеживать ошибки.
источник
Давайте посмотрим на разницу между тестируемым:
и не тестируемый контроллер:
Первый вариант имеет буквально 5 дополнительных строк кода, две из которых могут быть автоматически сгенерированы Visual Studio. После того, как вы настроили свою инфраструктуру внедрения зависимостей для замены конкретного типа
IMyDependency
во время выполнения - что для любой приличной структуры DI - еще одна строка кода - все просто работает, за исключением того, что теперь вы можете смоделировать и, таким образом, протестировать свой контроллер на содержание вашего сердца ,6 дополнительных строк кода для обеспечения возможности тестирования ... а ваши коллеги утверждают, что это "слишком много работы"? Этот аргумент не летит со мной, и он не должен летать с вами.
И вам не нужно создавать и реализовывать интерфейс для тестирования: например, Moq позволяет вам моделировать поведение конкретного типа для целей модульного тестирования. Конечно, это не принесет вам большой пользы, если вы не сможете внедрить эти типы в тестируемые вами классы.
Инъекция зависимости - одна из тех вещей, которые, как только вы это понимаете, задаются вопросом: «Как я работал без этого?». Это просто, это эффективно, и это просто имеет смысл. Пожалуйста, не допускайте, чтобы ваши коллеги не понимали, что нового может помешать сделать ваш проект тестируемым.
источник
Когда я пишу модульные тесты, я начинаю думать о том, что может пойти не так в моем коде. Это помогает мне улучшить дизайн кода и применять принцип единой ответственности (SRP). Кроме того, когда я возвращаюсь, чтобы изменить тот же код несколько месяцев спустя, это помогает мне подтвердить, что существующая функциональность не нарушена.
Существует тенденция использовать как можно больше чистых функций (безсерверные приложения). Модульное тестирование помогает мне изолировать состояние и писать чистые функции.
Сначала напишите модульные тесты для базового API, и, если у вас достаточно времени для разработки, вам также необходимо написать тесты для службы тонкого Web API.
TL; DR, модульное тестирование помогает улучшить качество кода и сделать будущие изменения в коде без риска. Это также улучшает читабельность кода. Используйте тесты вместо комментариев, чтобы высказать свою точку зрения.
источник
Суть и то, что вы должны аргументировать с неохотой, это то, что конфликта нет. Кажется, большая ошибка заключалась в том, что кто-то придумал идею «проектировать для тестирования» людей, которые ненавидят тестирование. Они должны были просто заткнуть рот или произнести это по-другому, например, «давайте потратим время, чтобы сделать это правильно».
Идея, что «вы должны реализовать интерфейс», чтобы сделать что-то тестируемое, ошибочна. Интерфейс уже реализован, он еще не объявлен в объявлении класса. Это вопрос распознавания существующих открытых методов, копирования их подписей в интерфейс и объявления этого интерфейса в объявлении класса. Нет программирования, нет изменений в существующей логике.
Видимо, у некоторых людей есть другое представление об этом. Я предлагаю вам сначала попытаться исправить это.
источник