модульные тесты что это

В этом руководстве вы получите некоторые практические рекомендации по написанию модульных тестов, чтобы создавать устойчивые и понятные тесты.

Почему именно модульные тесты?

Меньше времени на выполнение функциональных тестов

Функциональные тесты требуют большого количества ресурсов. Как правило, приходится открывать приложение и выполнять ряд действий, чтобы проверить ожидаемое поведение. Тест-инженеры не всегда знают, что это за действия, и им приходится обращаться к специалистам в этой области. Само тестирование может занимать несколько секунд, если это обычные изменения, или несколько минут для более масштабных изменений. Наконец, этот процесс необходимо повторять для каждого изменения, внесенного в систему.

Модульные тесты, с другой стороны, занимают миллисекунды, выполняются простым нажатием кнопки и не обязательно требуют знаний о всей системе в целом. Успешность прохождения теста зависит от средства выполнения теста, а не от пользователя.

Защита от регрессии

Дефекты регрессии вводятся при внесении изменений в приложение. Довольно часто тест-инженеры тестируют не только новую функцию, но и функции, существовавшие до этого, чтобы проверить, что эти функции по-прежнему работают должным образом.

С модульным тестированием можно повторно запускать весь набор тестов после каждой сборки или даже после изменения строки кода. Это дает вам уверенность, что ваш новый код не нарушил существующие функциональные возможности.

Исполняемая документация

Не всегда очевидно, что делает конкретный метод или как он себя ведет при определенных входных данных. Вы можете спросить себя: как поведет себя метод, если я передам ему пустую строку? А значение NULL?

Если у вас есть набор модульных тестов с понятными именами, каждый тест сможет четко объяснить, какими будут выходные данные для определенных входных данных. Кроме того, он сможет проверить, что это действительно работает.

Менее связанный код

Если код тесно связан, он плохо подходит для модульного тестирования. Без создания модульных тестов для кода это связывание может быть менее очевидным.

Когда вы пишете тесты для кода, вы естественным образом разделяете его, иначе его будет сложнее тестировать.

Характеристики хорошего модульного теста

Покрытие кода

Высокий процент покрытия кода зачастую связан с более высоким качеством кода. Однако само по себе измерение не может определить качество кода. Задание чрезмерно большого процента объема протестированного кода может снизить производительность. Представьте себе сложный проект с тысячами условных ветвей и представьте, что объем протестированного кода составляет 95 %. В настоящее время проект поддерживает объем протестированного кода в 90 %. Время, затрачиваемое на принятие всех пограничных вариантов в оставшихся 5 %, может оказаться очень значительным, а ценность предложения быстро сокращается.

Высокий процент объема протестированного кода не является индикатором успеха и не подразумевает высокое качество кода. Он представляет собой лишь объем кода, охваченного модульными тестами. Дополнительные сведения см. в статье Модульное тестирование объема протестированного кода.

Определимся с терминами

К сожалению, термин макет по отношению к тестированию часто употребляется неправильно. Следующие пункты определяют самые распространенные типы заполнителей при написании модульных тестов:

Заполнитель — это общий термин, который можно использовать для описания заглушки или макета объекта. Использование заглушки или макета зависит от контекста. Иными словами, заполнитель может быть заглушкой или макетом.

Макет. Макет объекта — это объект-заполнитель в системе, который решает, пройден ли модульный тест. Макет начинает существование как заполнитель, пока по нему не будет проведена проверка.

Заглушка — это управляемая замена существующей зависимости (или участника) в системе. С помощью заглушки можно протестировать код, не задействовав зависимость напрямую. По умолчанию заглушка выступает как заполнитель.

Рассмотрим следующий фрагмент кода:

Это будет пример заглушки, которая будет называться макетом. В данном случае это заглушка. Вы передаете Order, чтобы создать экземпляр Purchase (тестируемая система). Имя MockOrder также вводит в заблуждение, поскольку Order не является макетом.

Чтобы использовать его как макет, можно сделать нечто подобное:

В этом случае проверяется свойство заполнителя (выполняется проверка по нему), поэтому в приведенном выше фрагменте кода mockOrder является макетом.

Очень важно правильно разобраться в терминологии. Если вы будете называть заглушки макетами, другие разработчики не поймут ваших намерений.

В первую очередь следует помнить, что макеты отличаются от заглушек тем, что вы выполняете проверку по макету объекта, но не выполняете проверку по заглушке.

Рекомендации

Попробуйте не создавать зависимости в инфраструктуре при написании модульных тестов. Из-за этого тесты выполняются медленно и нестабильно. Зависимости следует использовать в интеграционных тестах. Чтобы избежать появления зависимостей в коде приложения, следуйте принципу явных зависимостей и используйте внедрение зависимостей. Вы также можете разместить модульные тесты в отдельном проекте, не содержащем интеграционных тестов. Это гарантирует отсутствие в проекте модульного теста ссылок на пакеты инфраструктуры или зависимостей от них.

Выбор имен для тестов

Имя теста должно состоять из трех частей:

Почему?

Тесты не просто проверяют работоспособность кода — они также предоставляют документацию. Просто посмотрев на набор модульных тестов, вы должны определить поведение кода, не глядя на сам код. Кроме того, если тест не пройден, вы сразу увидите, какие сценарии не соответствуют вашим ожиданиям.

Плохо:

Лучше:

Упорядочивание тестов

Упорядочивайте, действуйте, проверяйте — это общий шаблон для модульного тестирования. Как и предполагает название, он состоит из трех элементов.

Почему?

Удобочитаемость является одним из наиболее важных аспектов при написании тестов. Четкое разделение каждого из этих действий в тесте подчеркивает зависимости, необходимые для вызова кода, указывает, как вызывается ваш код и что вы пытаетесь проверить. Хотя некоторые шаги можно объединить и уменьшить размер теста, основной целью является удобочитаемость теста.

Плохо:

Лучше:

Пишите минималистичные тесты

Входные данные для модульного теста должны быть как можно проще, чтобы проверить поведение, которое вы тестируете в данный момент.

Почему?

Тесты, которые включают больше информации, чем требуется для прохождения, могут содержать больше ошибок и затруднять понимание намерения. При написании тестов необходимо сосредоточиться на поведении. Установка дополнительных свойств для моделей или использование ненулевых значений, когда это не требуется, только затрудняет проверку.

Плохо:

Лучше:

Избегайте «магических» строк

Именование переменных в модульных тестах так же важно, как именование переменных в рабочем коде, если не важнее. Модульные тесты не должны содержать «магических» строк.

Почему?

«Магические» строки могут запутать читателя тестов. Если строка выглядит необычно, у него может возникнуть вопрос, почему было выбрано определенное значение для параметра или возвращаемого значения. И ему придется рассматривать детали реализации, вместо того чтобы сосредоточиться на тесте.

При написании тестов старайтесь как можно яснее выразить свое намерение. В случае «магических» строк рекомендуется присваивать эти значения константам.

Плохо:

Лучше:

Избегайте логики в тестах

Почему?

При введении логики в набор тестов вероятность появления ошибок значительно возрастает. Меньше всего вам нужна ошибка в наборе тестов. Вы должны быть уверены, что тесты работают. Иначе вы не сможете им доверять. Если вы не доверяете тесту, он бесполезен. Если тест не пройден, вы должны знать, что с кодом действительно что-то не так и это нельзя игнорировать.

Если логика в тесте неизбежна, рекомендуется разбить тест на несколько тестов.

Плохо:

Лучше:

Выбирайте вспомогательные методы вместо установки и удаления

Почему?

В модульном тестировании Setup вызывается до всех модульных тестов в наборе. Хотя некоторые считают это полезным инструментом, в конечном итоге тесты сложно воспринимать. Каждый тест обычно имеет различные требования для запуска. К сожалению, Setup заставляет вас использовать одинаковые требования для всех тестов.

В xUnit удалены SetUp и TearDown в версии 2.x.

Плохо:

Лучше:

Избегайте нескольких действий

При написании тестов постарайтесь, чтобы каждый тест содержал только одно действие. Чтобы использовать только одно действие, можно применить следующие распространенные приемы.

Почему?

Для нескольких действий потребуются отдельные проверочные утверждения и нет гарантий, что все эти утверждения будут выполнены. В большинстве платформ модульного тестирования сбой одного из проверочных утверждений в модульном тесте приводит к тому, что все последующие тесты автоматически считаются непройденными. Это может вызывать путаницу, так как действующие функции будут казаться неработоспособными.

Плохо:

Лучше:

Проверяйте закрытые методы путем модульного тестирования открытых методов

В большинстве случаев вам не понадобится тестировать закрытые методы. Закрытые методы относятся к тонкостям реализации. Подумайте об этом так: закрытые методы никогда не существуют в изоляции. Рано или поздно у вас будет открытый метод, вызывающий закрытый метод в рамках его реализации. Вам следует думать о конечном результате открытого метода, который вызывает закрытый метод.

Рассмотрим следующий случай.

С этой точки зрения, если вы видите закрытый метод, найдите открытый метод и напишите тесты для него. Тот факт, что закрытый метод возвращает ожидаемый результат, вовсе не означает, что система, которая вызывает закрытый метод, использует этот результат правильно.

Используйте заглушки для статических ссылок

Один из принципов модульного тестирования — его полный контроль над тестируемой системой. Это может быть проблематичным, когда рабочий код вызывает статические ссылки (например, DateTime.Now ). Рассмотрим следующий код.

Как можно провести модульное тестирование этого кода? Вы можете попробовать следующий подход.

К сожалению, вы быстро поймете, что в тестах есть несколько проблем.

Для решения этих проблем вам потребуется ввести шов в рабочий код. Например, можно заключить код, который необходимо контролировать, в интерфейс, чтобы рабочий код зависел от этого интерфейса.

Источник

Что такое юнит-тесты и почему они так важны

Бывает, кодишь 10 минут, а дебажишь 2 часа. Чтобы такого не случилось, пилите юнит-тесты. Михаил Фесенко рассказал, как их правильно готовить.

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Oli Scarff / Staff / GettyImages

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Фесенко Михаил, можно просто Фес. Разработчик, раньше работал системным администратором, пишет на чём скажут, но пока писал на PHP, Go, Python, Bash. Сейчас работает в «Яндекс.Облаке», до этого работал во «ВКонтакте». Любит жену, кино и снимать видео =)

Юнит-тест (unit test), или модульный тест, — это программа, которая проверяет работу небольшой части кода. Разработчики регулярно обновляют сайты и приложения, добавляют фичи, рефакторят код и вносят правки, а затем проверяют, как всё работает.

Тестировать систему целиком после каждого обновления — довольно муторно и неэффективно. Поэтому обновлённые или исправленные части кода прогоняют через юнит-тесты.

Особенности юнит-тестов

На практике используют разные тесты — их разделяют по уровню абстракции с помощью пирамиды Майка Кона :

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Чем выше тест в пирамиде, тем больше частей программы он затрагивает. Высокоуровневые тесты «ближе к бизнесу»: они проверяют бизнес-логику и пользовательские процессы. А те, что внизу пирамиды, помогают найти проблемы в отдельных частях кода. Например, какую-нибудь функцию, которая генерирует имя файла.

В отличие от них, юнит-тесты нужны в следующих случаях:

Некоторые программисты пишут только юнит-тесты, а на интеграционные или E2E-тесты жалеют времени. На самом деле нужно покрывать систему всеми видами тестов, чтобы знать, как взаимодействуют друг с другом разные части программы, какие промежуточные результаты они выдают. Но в то же время, если юнит-тесты показывают ошибку, её покажет и интеграционный, и E2E-тест.

Процесс юнит-тестирования

Для юнит-тестирования подключают тестовые фреймворки — они позволяют «мокать», то есть имитировать функции. В коде больших проектов много зависимостей: одна функция вызывает другую и влияет на разные части программы. Но, как правило, достаточно проверить функции «в вакууме», отдельно от остального кода. Для этого и нужен тестовый фреймворк — он моделирует условия, в которых функция А вызывает функцию Б изолированно от других функций.

Простой пример: у нас есть функция на Go, которая получает id бэкапа и возвращает имя бэкап-файла:

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Протестируем её с помощью набора входных и выходных данных. Они должны учитывать все ситуации, поэтому не забываем про негативные кейсы — когда программа возвращает ошибку. Вот набор тестовых данных:

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

В первую очередь я прописал запрещённые данные (-1 и 0) и слишком большое значение (10200300). Когда пользователь их вводит, функция не должна возвращать результат. Вместо этого мы ждём сообщения об ошибке: BAD_ID или BACKUP_ID_TOO_BIG. Когда же функция получает валидный id, она выводит отформатированное имя файла, например Backup#000010.

А вот и код самого теста:

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Порой код для тестирования даже больше основного — и это норма. Но иногда всё-таки стоит задуматься, на самом ли деле тест должен быть таким объёмным. Я бы посоветовал покрывать тестами только те фрагменты кода, которые вы планируете менять. Или сложные части, которые, скорее всего, придётся чинить или поддерживать.

Некоторые разработчики мокают всё подряд. Из-за этого тесты становятся хрупкими, а код — сложным и непонятным. На самом деле для юнит-тестирования достаточно лишь немного переписать код, а огромные функции лучше разбить на более мелкие.

В старой хорошей книге «Экстремальное программирование» есть классная мысль: сначала пишите тест, а только потом программу. Это клёвый подход, но не все могут так делать (а кто-то просто не хочет тратить время).

Как покрыть код юнит-тестами

Есть разработчики, которые не проводят модульное тестирование: «Ой, у нас большой проект, и переписать 1000 строк под тесты или замокать их — слишком запарно». На самом деле покрыть код тестами несложно. Вот несколько советов.

Написали код — напишите тест. Я видел много проектов, в которых юнит-тесты писали по принципу «новый код — новый тест». Думаю, это правильный подход, ведь, когда добавляешь в программу что-то новое, она часто ломается. К тому же, если писать тесты сразу, не придётся переворачивать весь код, когда он разрастётся.

Есть более жёсткий принцип: новый код без тестов на ревью не принимается. Конечно, он работает, если сроки не горят, — иначе программист рефакторит или покрывает его тестами позже.

Используйте тестовый фреймворк. В тестировании не нужно изобретать велосипед. Для популярных языков уже есть готовые решения, поэтому достаточно вбить в поиске test frameworks, и вы получите целый список. Вот, например, результат для Python:

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Пишите простые тесты. Надо понимать, что происходит с входными данными и какой результат должна вернуть функция. Если непонятно — меняем нейминг и разбиваем функции на более мелкие, избавляемся от зависимостей. Пусть одна функция принимает результат, а другая возвращает. Так проще тестировать.

Допустим, у нас есть такая функция:

Её не нужно прогонять через юнит-тест, потому что тогда придётся мокать process_a, process_b и prepare_output. Тут нужен интеграционный тест, который проверит, как эти компоненты взаимодействуют между собой. Вообще, если код сложно покрывать юнит-тестами, используйте интеграционные — они проверяют общую работу системы, модуля или библиотеки.

Не забывайте про негативные тесты. Это the best practice. Что произойдёт, если передать в программу неправильные данные? Какую ошибку она выведет и выведет ли?

Покрывайте тестами все циклы и if-else. Этот совет касается кода, который нужно поддерживать. Если ему не следовать, на одной из итераций правок вы или ваш коллега просто всё сломаете.

Проверяйте качество тестов. Сделать это поможет мутационное тестирование. Мутационный фреймворк случайно меняет константы и значения в условных операторах и циклах, создаёт копию кода, в которой поочерёдно меняет условия. Например, было >= или было COUNT=3, а стало COUNT=10. Каждая замена тестируется: если код поменялся, а тесты не упали, значит, код не покрыт тестами.

На мутационное тестирование уходит много времени. Можно подключить плагин, который считает code coverage по тесту и выдаёт отчёт. Например, у нас покрыто тестами 43 тысячи строк кода, а 10 тысяч — нет. Значит, code coverage 81%. Но тут важен не только сам процент, но и качество — какие именно фрагменты кода и какими именно тестами покрыты. Например, не всё может быть под юнит-тестами — часть может перекрываться интеграционными.

Обеспечьте достаточный процент покрытия кода. Года три-четыре назад я был фанатиком стопроцентного покрытия. Конечно, безумно круто, когда ты всегда знаешь, что именно сломалось. Но в продакшне этого добиться сложно — да и не нужно. Исключение — маленькие проекты или «жёсткие» команды, для которых полное покрытие в приоритете.

На самом деле, code coverage в 70–90% — уже крутой показатель, но и меньше 70% — тоже плохо. И ещё важный момент: новый код не должен понижать уровень code coverage.

Проверить code coverage можно с помощью coveralls.io:

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Coveralls принимает результаты тестов и выдаёт отчёт: показывает процент покрытия и как он изменился с последнего теста.

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Не делайте хрупкие тесты. Если тест нестабильный и регулярно падает, его называют хрупким. Его результат может зависеть от дня недели, времени суток, чётности или нечётности запуска. Бывает, две функции работают параллельно и на итоговый результат влияет то, какая из них закончит выполняться первой. Такие функции лучше разбивать на несколько простых и тестировать по отдельности. Мокайте всё что нужно, чтобы сделать тест управляемым, но не переборщите — иначе код будет сложно поддерживать.

Допустим, мы написали юнит-тесты для двух функций. Но не учли, что первая функция сохраняет данные в глобалке, а вторая из-за этого меняет своё поведение. В результате первый тест проходит нормально, а второй падает или ведёт себя странно. А всё потому, что мы не сбросили состояние глобальной переменной.

Следите за скоростью тестов. Тесты должны работать быстро. Если они проверяют кусок кода 10–15 минут — разработчики устанут ждать и отключат их нафиг. Поэтому регулярно проверяйте скорость, ищите узкие места и оптимизируйте тесты. Если есть проблемы, подключитесь через дебаггер — возможно, основной код плохо оптимизирован и искать проблему нужно в продакшне.

Преимущества юнит-тестов

Если у вас ещё остались сомнения, писать юнит-тесты или нет, вот несколько аргументов за. Итак, чем полезны юнит-тесты.

Упрощают работу — находят ошибки, которые вы можете не заметить (меня это много раз спасало). Например, меняешь одну строчку, чтобы поправить логи, а ломается весь код. Благодаря тестам я узнавал об этом ещё до продакшна.

Понятно документируют код. Если вам неочевидно, как работает та или иная функция, можно пройти дальше по коду или открыть юнит-тест. По нему сразу видно, какие параметры принимает функция и что отдаёт после выполнения. Это упрощает жизнь тем, кто работает с чужим кодом.

Помогают ничего не сломать при рефакторинге. Бывает, что код написан непонятно и ты не можешь его отрефакторить, потому что наверняка что-то сломаешь в продакшне. А с тестами код можно смело рефакторить.

Упрощают разработку. Кажется, что юнит-тесты всё усложняют, ведь нужно написать в два раз больше кода — не только функцию, но и тест к ней. Но я много раз убеждался: когда пишешь код без тестов, потом тратишь гораздо больше времени на поиск и исправление ошибок.

Бывает, бац-бац — и в продакшн, а потом понеслось: исправляешь код первый, второй, третий раз. И постоянно вспоминаешь, как тестировать его вручную. У меня даже были файлики с входными данными для таких проверок. Тогда я тестировал программы вручную, по бумажке, и тратил на это уйму времени. А если бы написал юнит-тест, нашёл бы эти баги сразу и не переписывал код по несколько раз.

В коммерческой разработке без юнит-тестов никуда

Сейчас в коммерческой разработке без тестов почти не работают — а в большинстве компаний от разработчиков даже требуют покрывать код юнит-тестами. Везде, где я работал в последние несколько лет, тоже было такое правило. Ведь если в команде кто-то факапит, то может развалиться вся работа — а тестирование как раз защищает от краха.

Современные компании подписывают SLA — гарантируют работоспособность сервиса. Если продукт упадёт, бизнесу придётся заплатить деньги. Поэтому лучше подождать тестов и не катить код, который положит весь продакшн. Даже если сайт или приложение пролежат всего две минуты, это ударит по репутации и дорого обойдётся компании.

Чтобы лучше понять юнит-тесты, изучите тестовые фреймворки вашего языка. А потом найдите крупные open-source-проекты, которые их используют, и посмотрите, как они работают. Можно даже скачать проект и поиграть с тестами, чтобы глубже погрузиться в тему.

Чтобы познать тонкости разработки и тестирования приложений, лучше сразу учиться у практикующих профессионалов. Приходите в университет Skillbox, выбирайте курс и осваивайте программирование под присмотром экспертов.

Источник

Юнит-тестирование для чайников

Даже если вы никогда в жизни не думали, что занимаетесь тестированием, вы это делаете. Вы собираете свое приложение, нажимаете кнопку и проверяете, соответствует ли полученный результат вашим ожиданиям. Достаточно часто в приложении можно встретить формочки с кнопкой “Test it” или классы с названием TestController или MyServiceTestClient.

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

То что вы делаете, называется интеграционным тестированием. Современные приложения достаточно сложны и содержат множество зависимостей. Интеграционное тестирование проверяет, что несколько компонентов системы работают вместе правильно.

Оно выполняет свою задачу, но сложно для автоматизации. Как правило, тесты требуют, чтобы вся или почти вся система была развернута и сконфигурирована на машине, на которой они выполняются. Предположим, что вы разрабатываете web-приложение с UI и веб-сервисами. Минимальная комплектация, которая вам потребуется: браузер, веб-сервер, правильно настроенные веб-сервисы и база данных. На практике все еще сложнее. Разворачивать всё это на билд-сервере и всех машинах разработчиков?

We need to go deeper

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это
Давайте сначала спустимся на предыдущий уровень и убедимся, что наши компоненты работают правильно по-отдельности.

Обратимся к википедии:

Модульное тестирование, или юнит-тестирование (англ. unit testing) — процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы.

Идея состоит в том, чтобы писать тесты для каждой нетривиальной функции или метода. Это позволяет достаточно быстро проверить, не привело ли очередное изменение кода к регрессии, то есть к появлению ошибок в уже оттестированных местах программы, а также облегчает обнаружение и устранение таких ошибок.

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Таким образом, юнит-тестирование – это первый бастион на борьбе с багами. За ним еще интеграционное, приемочное и, наконец, ручное тестирование, в том числе «свободный поиск».

Нужно ли все это вам? С моей точки зрения ответ: «не всегда».

Не нужно писать тесты, если

В первых трех случаях по объективным причинам (сжатые сроки, бюджеты, размытые цели или очень простые требования) вы не получите выигрыша от написания тестов.

Последний случай рассмотрим отдельно. Я знаю только одного такого человека, и если вы не узнали себя на фото ниже, то у меня для вас плохие новости.

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Любой долгосрочный проект без надлежащего покрытия тестами обречен рано или поздно быть переписанным с нуля

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Проекты первого типа – крепкий орешек, с ними работать тяжелее всего. Обычно их рефакторинг по стоимости равен или превышает переписывание с нуля.

Почему есть проекты второго типа?

Коллеги из ScrumTrek уверяют, что всему виной темная сторона кода и властелин Дарт Автотестиус. Я убежден, что это очень близко к правде. Бездумное написание тестов не только не помогает, но вредит проекту. Если раньше у вас был один некачественный продукт, то написав тесты, не разобравшись в этой теме, вы получите два. И удвоенное время на сопровождение и поддержку.

Для того чтобы темная сторона кода не взяла верх, нужно придерживаться следующих основных правил.
Ваши тесты должны:

Выберите логическое расположение тестов в вашей VCS

Только так. Ваши тесты должны быть частью контроля версий. В зависимости от типа вашего решения, они могут быть организованы по-разному. Общая рекомендация: если приложение монолитное, положите все тесты в папку Tests; если у вас много разных компонентов, храните тесты в папке каждого компонента.

Выберите способ именования проектов с тестами

У такого способа именования есть дополнительный сайд-эффект. Вы сможете использовать паттерн *.Tests.dll для запуска тестов на билд-сервере.

Используйте такой же способ именования для тестовых классов

У вас есть класс ProblemResolver? Добавьте в тестовый проект ProblemResolverTests. Каждый тестирующий класс должен тестировать только одну сущность. Иначе вы очень быстро скатитесь в унылое го во второй тип проектов (с тестами, которые никто не запускает).

Выберите «говорящий» способ именования методов тестирующих классов

TestLogin – не самое лучшее название метода. Что именно тестируется? Каковы входные параметры? Могут ли возникать ошибки и исключительные ситуации?

На мой взгляд, лучший способ именования методов такой: [Тестируемый метод]_[Сценарий]_[Ожидаемое поведение].
Предположим, что у нас есть класс Calculator, а у него есть метод Sum, который (привет, Кэп!) должен складывать два числа.
В этом случае наш тестирующий класс будет выглядеть так:

Такая запись понятна без объяснений. Это спецификация к вашему коду.

Выберите тестовый фреймворк, который подходит вам

Вне зависимости от платформы не стоит писать велосипеды. Я видел много проектов, в которых автоматические тесты (в основном, не юнит, а приемочные) запускались из консольного приложения. Не надо этого делать, все уже сделано за вас.

Что тестировать, а что – нет?

Одни говорят о необходимости покрытия кода на 100%, другие считают это лишней тратой ресурсов.
Мне нравится такой подход: расчертите лист бумаги по оси X и Y, где X – алгоритмическая сложность, а Y – количество зависимостей. Ваш код можно разделить на 4 группы.
модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Рассмотрим сначала экстремальные случаи: простой код без зависимостей и сложный код с большим количеством зависимостей.

Придерживайтесь единого стиля написания тела теста

Такая форма записи гораздо легче читается, чем

А значит, этот код проще поддерживать.

Тестируйте одну вещь за один раз

Каждый тест должен проверять только одну вещь. Если процесс слишком сложен (например, покупка в интернет магазине), разделите его на несколько частей и протестируйте их отдельно.
Если вы не будете придерживаться этого правила, ваши тесты станут нечитаемыми, и вскоре вам окажется очень сложно их поддерживать.

Борьба с зависимостями

До сих пор мы тестировали калькулятор. У него совсем нет зависимостей. В современных бизнес-приложениях количество таких классов, к сожалению, мало.
Рассмотрим такой пример.

Фабрика в этом примере берет данные о конкретной реализации AccountData из файла конфигурации, что нас абсолютно не устраивает. Мы же не хотим поддерживать зоопарк файлов *.config. Более того, настоящие реализации могут зависеть от базы данных. Если мы продолжим в том же духе, то перестанем тестировать только методы контроллера и начнем вместе с ними тестировать другие компоненты системы. Как мы помним, это называется интеграционным тестированием.
Чтобы не тестировать все вместе, мы подсунем фальшивую реализацию (fake).
Перепишем наш класс так:

Теперь у контроллера появилась новая точка входа, и мы можем передать туда другие реализации интерфейсов.

Fakes: stubs & mocks

Мы переписали класс и теперь можем подсунуть контроллеру другие реализации зависимостей, которые не станут лезть в базу, смотреть конфиги и т.д. Словом, будут делать только то, что от них требуется. Разделяем и властвуем. Настоящие реализации мы должны протестировать отдельно в своих собственных тестовых классах. Сейчас мы тестируем только контроллер.

Выделяют два типа подделок: стабы (stubs) и моки (mock).
Часто эти понятия путают. Разница в том, что стаб ничего не проверяет, а лишь имитирует заданное состояние. А мок – это объект, у которого есть ожидания. Например, что данный метод класса должен быть вызван определенное число раз. Иными словами, ваш тест никогда не сломается из-за «стаба», а вот из-за мока может.
С технической точки зрения это значит, что используя стабы в Assert мы проверяем состояние тестируемого класса или результат выполненного метода. При использовании мока мы проверяем, соответствуют ли ожидания мока поведению тестируемого класса.

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Тестирование состояния и тестирование поведения

Почему важно понимать, казалось бы, незначительную разницу между моками и стабами? Давайте представим, что нам нужно протестировать автоматическую систему полива. Можно подойти к этой задаче двумя способами:

Тестирование состояния

Запускаем цикл (12 часов). И через 12 часов проверяем, хорошо ли политы растения, достаточно ли воды, каково состояние почвы и т.д.

Тестирование взаимодействия

Установим датчики, которые будут засекать, когда полив начался и закончился, и сколько воды поступило из системы.
Стабы используются при тестировании состояния, а моки – взаимодействия. Лучше использовать не более одного мока на тест. Иначе с высокой вероятностью вы нарушите принцип «тестировать только одну вещь». При этом в одном тесте может быть сколько угодно стабов или же мок и стабы.

Изоляционные фреймвоки

В примере выше я использовал фреймворк Moq для создания моков и стабов. Довольно распространен фреймворк Rhino Mocks. Оба фреймворка — бесплатные. На мой взгляд, они практически эквивалентны, но Moq субъективно удобнее.

На рынке есть также два коммерческих фреймворка: TypeMock Isolator и Microsoft Moles. На мой взгляд они обладают чрезмерными возможностями подменять невиртуальные и статические методы. Хотя при работе с унаследованным кодом это и может быть полезно, ниже я опишу, почему все-таки не советую заниматься подобными вещами.

Шоукейсы перечисленных изоляционных фреймворков можно посмотреть тут. А информацию по техническим аспектам работы с ними легко найти на Хабре.

Тестируемая архитектура

Вернемся к примеру с контроллером.

Здесь мы отделались «малой кровью». К сожалению, не всегда все бывает так просто. Давайте рассмотрим основные случаи, как мы можем внедрить зависимости:

Инъекция в конструктор

Добавляем дополнительный конструктор или заменяем текущий (зависит от того, как вы создаете объекты в вашем приложении, используете ли IOC-контейнер). Этим подходом мы воспользовались в примере выше.

Инъекция в фабрику

Setter можно дополнительно «спрятать» от основного приложения, если выделить интерфейс IUserManagerFactory и работать в продакшн-коде по интерфейсной ссылке.

Подмена фабрики

Вы можете подменить всю фабрику целиком. Это потребует выделение интерфейса или создание виртуальной функции, создание объектов. После этого вы сможете переопределить фабричные методы так, чтобы они возвращали ваши подделки.

Переопределение локального фабричного метода

Если зависимости инстанцируются прямо в коде явным образом, то самый простой путь – выделить фабричный protected-метод CreateObjectName() и переопределить его в классе-наследнике. После этого тестируйте класс-наследник, а не ваш первоначально тестируемый класс.
Например, мы решили написать расширяемый калькулятор (со сложными действиями) и начали выделять новый слой абстракции.

Мы не хотим тестировать класс Multiplier, для него будет отдельный тест. Перепишем код так:

Код намеренно упрощен, чтобы акцентировать внимание именно на иллюстрации способа. В реальной жизни вместо калькулятора, скорее всего, будут DataProvider’ы, UserManager’ы и другие сущности с гораздо более сложной логикой.

Тестируемая архитектура VS OOP

Многие разработчики начинают жаловаться, дескать «этот ваш тестируемый дизайн» нарушает инкапсуляцию, открывает слишком много. Я думаю, что существует только две причины, когда это может вас беспокоить:

Серьезные требования к безопасности
Производительность

Существует ряд задач, когда архитектурой приходится жертвовать в угоду производительности, и для кого-то это становится поводом отказаться от тестирования. В моей практике докинуть сервер/проапгрейдить железо всегда было дешевле, чем писать нетестируемый код. Если у вас есть критический участок, вероятно, стоит переписать его на более низком уровне. Ваше приложение на C#? Возможно, есть смысл собрать одну неуправляемую сборку на С++.

Работа с унаследованным кодом

Под «унаследованным» мы будем понимать код без тестов. Качество такого кода может быть разным. Несколько советов, как можно покрыть его тестами.

Архитектура тестируема

Нам повезло, прямых созданий классов и мясорубки нет, а принципы SOLID соблюдаются. Нет ничего проще – создаем тестовые проекты, и шаг за шагом покрываем приложение, используя принципы, описанные в статье. В крайнем случае, нам придется добавить пару сеттеров для фабрик и выделить несколько интерфейсов.

Архитектура не тестируема

У нас есть жесткие связи, костыли и прочие радости жизни. Нам предстоит рефакторинг. Как правильно проводить комплексный рефакторинг – тема, выходящая далеко за рамки этой статьи.
Стоит выделить основное правило. Если вы не меняете интерфейсов – все просто, методика идентична. А вот если вы задумали большие перемены, следует составить граф зависимостей и разбить ваш код на отдельные более мелкие подсистемы (надеюсь, что это возможно). В идеале должно получиться примерно так: ядро, модуль #1, модуль #2 и т.д.
После этого выберите жертву. Только не начинайте с ядра. Возьмите сначала что-то поменьше: то, что вы способны отрефакторить за разумное время. Покрывайте эту подсистему интеграционными и/или приемочными тестами. А когда закончите, сможете покрыть эту часть юнит-тестами. Рано или поздно, шаг за шагом, вы должны преуспеть.
Будьте готовы, что сделать это быстро скорее всего не получится. Вам придется проявить волевые качества.

Поддержка тестов

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Не относитесь к своим тестам как к второсортному коду. Многие начинающие разработчики ошибочно полагают, что DRY, KISS и все остальное – это для продакшна. А в тестах допустимо все. Это не верно. Тесты – такой-же код. Разница только в том, что у тестов другая цель – обеспечить качество вашего приложения. Все принципы, применямые в разработке продакшн-кода могут и должны применяться при написании тестов.
Есть всего три причины, почему тест перестал проходить:

Уделяйте внимание поддержке ваших тестов, чините их вовремя, удаляйте дубликаты, выделяйте базовые классы и развивайте API тестов. Можно завести шаблонные базовые тестовые классы, которые обязывают реализовать набор тестов (например CRUD). Если делать это регулярно, то вскоре это не будет занимать много времени.

Как «измерить» прогресс

Для измерения успешности внедрения юнит-тестов в вашем проекте следует использовать две метрики:

Первая показывает, есть ли у наших действий результат, или мы впустую расходуем время, которое могли бы потратить на фичи. Вторая – как много нам еще предстоит сделать.

Test First?

модульные тесты что это. Смотреть фото модульные тесты что это. Смотреть картинку модульные тесты что это. Картинка про модульные тесты что это. Фото модульные тесты что это

Я умышленно не касался этой темы до самого конца. С моей точки зрения Test First – хорошая практика, обладающая рядом неоспоримых преимуществ. Однако, по тем или иным причинам, иногда я отступаю от этого правила и пишу тесты после того, как готов код.

На мой взгляд, «как писать тесты» гораздо важнее, чем «когда это делать». Делайте, как вам удобно, но не забывайте: если вы начинаете с тестов, то получаете архитектуру «в придачу». Если вы сначала пишете код, вам возможно, придется его менять, чтобы сделать тестируемым.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *