Единственный способ назвать свой код «хорошим» - это полностью протестировать его.
Предлагаю ли я 100% тестовое покрытие? Нет, я этого требую. Каждую строку кода, который вы пишете, следует тестировать. Период.
Я не хочу, чтобы руководство требовало 100% тестирования. Я хочу, чтобы ваша совесть считала это делом чести.
Как насчет того, что если у вас есть 100%, вы можете с уверенностью провести зверский рефакторинг, когда захотите.
Я взял приведенные выше цитаты из разговора в Твиттере, который дядя Боб Мартин вел со своими последователями.
Он очень увлечен покрытием кода и модульным тестированием. Мы все можем быть не такими суровыми, как дядя Боб, но все мы определенно должны хотя бы немного увлечься модульным тестированием.
И эта страсть должна проистекать из желания писать хороший код. Я собираюсь доказать, что единственный способ назвать свой код «хорошим» - это если он полностью прошел модульное тестирование.
Если вы пишете и доставляете код, который не прошел полное модульное тестирование, вы должны почувствовать, что идете по улице голым. Вы должны чувствовать себя незащищенными и как будто все смотрят на вас.
Вы должны твердо верить, что ваш код потерпит неудачу, если он не будет полностью охвачен модульными тестами.
Что такое модульное тестирование
Модульное тестирование - это тестирование кода, чтобы убедиться, что он выполняет поставленную задачу. Он проверяет код на самом низком уровне - отдельные методы ваших классов.
Это ключ к написанию чистого, поддерживаемого кода. Если вы сконцентрируетесь на написании кода, который легко тестируется, вы не сможете не получить развязанный, чистый, высококачественный код, который легко поддерживать. Что не нравится?
Но иногда возникают вопросы об определении терминов, когда дело доходит до модульного тестирования. Например, что такое «единица»? Что значит «издевательство»? Как мне узнать, действительно ли я провожу модульное тестирование?
В этой статье мы расскажем, что означают эти термины.
Что такое единица?
Первый вопрос, который возникает при обсуждении модульного тестирования: что такое модуль? Вы не можете проводить модульное тестирование, не зная, что такое модуль.
Когда дело доходит до модульного тестирования, я рассматриваю модуль как любой дискретный модуль кода, который можно тестировать изолированно. Это может быть что-то столь же простое, как автономная процедура, но обычно это будет отдельный класс и его методы.
Класс - это основная дискретная сущность кода многих современных языков и, таким образом, базовые строительные блоки вашего кода. Это структуры данных, которые при совместном использовании образуют систему.
В мире модульного тестирования этот класс обычно называют тестируемым классом (CUT) или тестируемой системой (SUT).
Вы увидите, что эти термины используются широко - до такой степени, что настоятельно рекомендуется использовать CUT в качестве имени переменной для ваших тестируемых классов.
Определение: Единица - это любой объект кода, который может быть протестирован изолированно, обычно это класс.
Я провожу модульное тестирование?
Итак, когда вы выполняете модульное тестирование, вы обычно тестируете классы (и в целях обсуждения это будет предположением в дальнейшем).
Но важно отметить, что при модульном тестировании класса вы тестируете модуль только для данного класса. Модульное тестирование всегда выполняется изолированно, то есть тестируемый класс должен быть полностью изолирован от любых других классов или любых других систем.
Если вы тестируете класс и вам нужна какая-то внешняя сущность, вы больше не занимаетесь модульным тестированием. Класс является «тестируемым» только тогда, когда его зависимости могут быть и являются «фальшивыми» и, следовательно, тестируемыми, без каких-либо его реальных внешних зависимостей.
Итак, если вы запускаете то, что, по вашему мнению, является модульным тестом, и этот тест требует доступа к базе данных, файловой системе или любой другой внешней системе, то вы остановили модульное тестирование и начали интеграционное тестирование.
Я хочу прояснить одну вещь. Нет ничего постыдного в проведении интеграционного тестирования. Интеграционное тестирование действительно важно и должно быть выполнено. Фреймворки модульного тестирования часто являются очень хорошим способом проведения интеграционного тестирования.
Я не хочу, чтобы у вас сложилось впечатление, что, поскольку интеграция не является модульным тестированием, вам не следует ее делать - как раз наоборот. Тем не менее, это важное различие.
Дело здесь в том, чтобы распознать, что такое модульные тесты, и попытаться написать их, когда они предназначены для написания. Обязательно пишите интеграционные тесты, но не пишите их вместо модульного тестирования.
Подумайте об этом так. Каждая среда модульного тестирования создает исполняемый файл теста. Если вы не можете взять этот тестовый исполняемый файл и успешно запустить его на материнском компьютере в каталоге, доступном только для чтения, значит, вы больше не проводите модульное тестирование.
Определение: Модульное тестирование - это процесс тестирования отдельного класса изолированно, полностью независимо от любых его фактических зависимостей.
Определение: Интеграционное тестирование - это проверка отдельного класса вместе с одной или несколькими его фактическими внешними зависимостями.
Что такое структура изоляции?
Обычно разработчики использовали термин mocking framework для описания кода, который предоставляет поддельные сервисы, позволяющие изолированно тестировать классы.
Однако, как мы увидим ниже, mock - это особый вид поддельного класса вместе с заглушками. Таким образом, вероятно, правильнее использовать термин изоляционная структура вместо имитирующая структура.
Полезный каркас изоляции позволит легко создавать оба типа подделок - моки и заглушки.
Подделки позволяют тестировать класс изолированно, предоставляя реализации зависимостей, не требуя реальных зависимостей.
Определение: каркас изоляции - это набор кода, который позволяет легко создавать подделки.
Определение: Поддельный класс - это любой класс, который предоставляет функциональные возможности, достаточные для того, чтобы делать вид, что это зависимость, необходимая для тестируемого класса. Есть два вида фейков: заглушки и моки.
Если вы хотите узнать об этом подробнее, я настоятельно рекомендую вам прочитать Искусство модульного тестирования: с примерами в .Net Роя Ошерова. Или вы можете послушать, как Рой разговаривает со Скоттом Хансельманом в подкасте Hanselminutes.
Если вы хотите стать супер-фанатиком, возьмите копию xUnit Test Patterns: Refactoring Test Code Джерарда Месароса.
Этот весомый фолиант представляет собой образец модульного тестирования, в котором излагается полная таксономия тестов и шаблонов тестирования. Это не для слабонервных, но, если вы прочитаете эту книгу, вы узнаете все, что нужно знать, и даже немного.
Заглушки
Заглушка - это класс, который делает абсолютный минимум, чтобы казаться фактической зависимостью для тестируемого класса. Он не обеспечивает функциональных возможностей, требуемых тестом, кроме как для реализации заданного интерфейса или унаследованного от заданного базового класса.
Когда CUT вызывает это, заглушка обычно ничего не делает. Заглушки являются второстепенными для тестирования CUT и существуют исключительно для того, чтобы позволить CUT работать.
Типичный пример - заглушка, предоставляющая услуги регистрации. CUT может потребоваться реализация, скажем, интерфейса ILogger для выполнения, но ни один из тестов не заботится о ведении журнала.
Вы специально не хотите, чтобы CUT ничего регистрировал. Таким образом, заглушка претендует на роль службы ведения журнала, реализуя интерфейс, но эта реализация на самом деле ничего не делает. Его методы реализации могут быть пустыми.
Более того, хотя заглушка может возвращать данные, чтобы CUT работал и работал, она никогда не может предпринять никаких действий, которые не позволят пройти тест. Если да, то он перестает быть заглушкой и становится имитацией.
Определение: заглушка - это подделка, которая не влияет на прохождение или невыполнение теста и существует исключительно для того, чтобы позволить запустить тест.
Издевается
Моки немного сложнее. Моки делают то же, что и заглушки, поскольку они предоставляют фальшивую реализацию зависимости, необходимой для CUT.
Однако они выходят за рамки простого заглушки, поскольку записывают взаимодействие между собой и CUT. Мок хранит записи обо всех взаимодействиях с CUT и отчитывается, пройдя тест, если CUT вел себя правильно, и провалил тест, если это не так.
Таким образом, это макет, а не сам CUT, определяет, прошел ли тест или нет.
Вот пример.
Допустим, у вас есть класс под названиемWidgetProcessor. У него две зависимости: ILogger и IVerifier. Чтобы протестировать WidgetProcessor, вам нужно подделать обе эти зависимости.
Однако, чтобы протестировать WidgetProcessor, вы захотите провести два теста: один, в котором вы вставляете ILogger и тестируете взаимодействие с IVerifier, а другой, когда вы вставляете заглушку IVerifier и тестируете взаимодействие с ILogger.
Оба требуют подделки, но в каждом случае вы предоставите класс-заглушку для одного и фиктивный класс для другого.
Давайте подробнее рассмотрим первый сценарий, в котором мы убираем ILogger и используем имитацию для IVerifier. Заглушка, которую мы обсуждали; вы либо пишете пустую реализацию ILogger, либо используете каркас изоляции для реализации интерфейса, который ничего не делает.
Однако фейк для IVerifier становится немного интереснее - ему нужен mock-класс. Скажем, процесс проверки виджета состоит из двух этапов. Сначала процессору необходимо увидеть, есть ли виджет в системе, а затем, если это так, процессору необходимо проверить, правильно ли настроен виджет.
Таким образом, если вы тестируетеWidgetProcessor, вам нужно запустить тест, который проверяет, выполняет ли WidgetProcessor второй вызов, если он получает True обратно из первого вызова.
Этот тест потребует от фиктивного класса выполнения двух действий. Во-первых, он должен вернуть True из первого вызова, а затем отслеживать, был ли выполнен результирующий вызов конфигурации.
Затем задачей фиктивного класса становится предоставление информации о прохождении / отказе. Если второй вызов, сделанный после первого, возвращает True, тогда тест проходит; в противном случае тест не пройден.
Вот что делает этот фальшивый класс имитацией: сам макет содержит информацию, которую необходимо проверить на соответствие критериям годен / не годен.
Определение: макет - это подделка, которая отслеживает поведение тестируемого класса и проходит или не проходит тест в зависимости от этого поведения.
Большинство фреймворков изоляции включают возможность всестороннего и сложного отслеживания того, что именно происходит внутри фиктивного класса.
Например, имитаторы могут не только определить, был ли вызван данный метод, но они также могут отслеживать количество вызовов данных методов и параметры, которые передаются этим вызовам.
Они могут определить и решить, вызывается ли то, чего не должно быть, или не вызывается то, что должно быть. В рамках настройки тестирования вы можете точно сказать макету, чего ожидать и что потерпеть неудачу, если эта точная последовательность событий и параметров не выполняется, как планировалось.
Заглушки относительно просты в создании и использовании, но моки могут быть довольно сложными.
Но опять же, весь смысл здесь в том, чтобы тестировать ваши классы изолированно; вы хотите, чтобы ваш CUT мог выполнять свои обязанности без каких-либо внешних, реальных, внешних зависимостей.
Итак, окончательное определение:
Определение : Модульное тестирование - это тестирование отдельного объекта кода, когда он полностью изолирован от его зависимостей.
Зачем нужно модульное тестирование?
Я считаю, что модульное тестирование вызывает большое сопротивление. Многие разработчики считают это пустой тратой времени или просто отсрочкой завершения проекта в установленные сроки. Они чувствуют, что не могут получить от этого никакой пользы.
Я не мог больше не согласиться. Вот почему.
Модульное тестирование обнаружит ошибки
Независимо от того, занимаетесь ли вы разработкой через тестирование и сначала пишете тесты, пишете тесты по ходу дела или пишете тесты спустя много времени после написания кода, модульное тестирование обнаружит ошибки.
Когда вы напишете полный набор тестов, определяющих ожидаемое поведение для данного класса, будет обнаружено все в этом классе, которое не ведет себя так, как ожидалось.
Модульное тестирование избавит от ошибок
Полный и тщательный набор модульных тестов поможет гарантировать, что любые ошибки, которые закрадываются в ваш код, будут немедленно обнаружены.
Внесите изменение, которое вводит ошибку, и ваши тесты могут выявить ее в следующий раз, когда вы запустите свои тесты. Если вы обнаружите ошибку, выходящую за рамки вашего набора модульных тестов, вы можете написать для нее тест, чтобы гарантировать, что ошибка никогда не вернется.
Модульное тестирование экономит время
Экономит время модульное тестирование или нет - это наиболее спорное понятие о модульном тестировании. Большинство разработчиков считают, что написание модульных тестов занимает больше времени, чем экономит. Я так не думаю - на самом деле, я утверждаю обратное.
Написание модульных тестов помогает с самого начала убедиться, что ваш код работает так, как задумано. Модульные тесты определяют, что должен делать ваш код, и поэтому вам не нужно тратить время на написание кода, который делает то, чего не должен делать.
Каждый модульный тест становится регрессионным тестом, гарантирующим, что все будет работать так, как задумано, пока вы разрабатываете. Они помогают гарантировать, что последующие изменения ничего не сломают.
Они помогают гарантировать, что то, что вы пишете в первый раз, будет правильным. Все эти преимущества экономят время как в краткосрочной, так и в долгосрочной перспективе.
И если вы думаете об этом, вы уже тестируете свой код во время его написания. Может быть, вы напишете простое консольное приложение. По крайней мере, вы компилируете и видите, как он работает.
Никто не проверяет код, который, по их мнению, не работает, и вы должны сделать что-то, чтобы заставить себя думать, что он работает. Потратьте это время на написание модульных тестов, и вы отделите рабочее кодирование от набора регрессионных тестов.
Модульное тестирование дает душевное спокойствие
Наличие полного, полного и тщательного набора тестов, охватывающих полную функциональность вашего кода, может быть труднодостижимым, но его наличие даст вам душевное спокойствие.
Вы можете запустить все эти тесты и знать, что ваш код работает так, как должен. Вы можете провести рефакторинг и изменить код, зная, что если вы что-нибудь сломаете, то сразу узнаете.
Знание состояния вашего кода, того, что он работает, и что вы можете обновлять и улучшать его, не опасаясь, - это отличная вещь.
Любой код устарел, но вы можете уберечь свой код от того, чтобы он когда-либо действительно стал унаследованным кодом. Есть несколько способов определить устаревший код, но один из них: «Код, к которому вы боитесь прикоснуться». Если в вашем коде есть модульные тесты, ему сложно превратиться в устаревший код.
У многих из нас есть фрагмент кода, к которому мы боимся прикоснуться, но с модульным тестированием такого кода не будет. Модульное тестирование устраняет этот страх прикосновения к коду.
В книге Эффективная работа с устаревшим кодом Майкл Фезерс заходит так далеко, что определяет устаревший код как любой код, не имеющий модульных тестов. Хотите, чтобы ваш код не превратился в устаревший? Напишите для него модульные тесты.
Модульное тестирование документирует правильное использование класса
Одним из преимуществ модульного тестирования является то, что ваши тесты могут определять для последующих разработчиков, как следует использовать класс.
По сути, модульные тесты становятся простыми примерами того, как работает ваш код, что он должен делать, и как правильно использовать тестируемый код. Потребители вашего кода могут обратиться к вашим модульным тестам за информацией о том, как правильно заставить ваш код делать то, что он должен делать.
Заключение
Хорошо, если все это не убеждает вас проводить модульное тестирование, я не знаю, что будет.
Я надеюсь, что после прочтения вы получите лучшее представление о том, что такое модульное тестирование и почему вы должны его проводить. Думаю, вам будет сложно найти кого-нибудь, кто сожалел бы о времени, потраченном на написание модульных тестов. Таким образом, если вы не пишете модульные тесты, подумайте о том, чтобы начать прямо сейчас.