Что включает в себя модульное тестирование
Модульное тестирование в современных командах
Для кого эта статья?
Для каких команд это подойдёт?
Я хотела бы показать вам процесс внедрения модульного тестирования для 2-х типов команд. В командах, которые работают по гибкой методологии и в командах, которые выбрали для себя каскадный стиль работы.
Ниже на диаграмме показано схематическое устройство команд по ролям:
Из этой схемы можно заключить то, что проджект менеджер, получая задачи от держателя продукта, согласовывает их со всеми участниками процесса и по результатам включает (или нет) их в спринт.
Чем модульное тестирование отличается от обычного?
Теперь хотелось бы рассказать что же такое модульное тестирование и как это реализовано в нашем случае:
Начну с того, что специалистами модульного тестирования являются эксперты в своих областях (под этим чаще всего понимается конкретная система). Они обладают широчайшим бэк граундом и необходимыми техническими навыками. Каждая система требует своих знаний, где-то это экспертные знания Pl/SQL, где-то конкретных языков разработки, а где-то только многолетний опыт работы с конкретной системой. Мы не затрагиваем front-end системы. Мы стоим на страже систем middle и back уровней.
Наверное ни для кого не секрет, что разработка/доработка любой фичи включает в себя изменения в системах на различных уровнях. Каждый модульный тестировщик проверяет новый функционал только в рамках своей системы. Тестируется код, соответствие контрактов без применения интерфейса. Выборка тестовых данных также происходит не вслепую, а на основе принципа минимального набора данных, покрывающего максимальное количество вариации выходных параметров. Для нас главное функциональная и техническая правильность написанного кода.
Первое отличие модульного тестирования от других видов заключается в том, что мы смотрим в код и ориентируемся на неизменность зафиксированных контактов/выходных параметров. Если они зафиксированы и не нарушаются, то в процессе интеграции систем фича включается без проблем.
И второе — это существенное снижение времени на тестирование доработки за счет экспертизы. Интеграционное тестирование смотрит фичу целиком, мы же рассматриваем ее по частям. Какие задачи решает модульное тестирование? Исходя из всего вышесказанного можно подвести черту и ответить на вопрос: какие же задачи решает модульное тестирование?
Польза внедрения модульного тестирования
После прочтения всего этого возникнет вопрос, а какие плюсы от внедрения модульного тестирования можно получить? Ранее я привела 2 схемы работы команд, и теперь опишу применительно к каждой из них:
Команда, работающая по аджайл методологии. Ниже на схеме приведено место модульного тестирования в команде такого типа:
В данном случае модульное тестирование подключается на поздней стадии разработки, когда происходит фактически уже «вылизывание» кода, а вся основная функциональность разработана. Модульному тестировщику НЕ НАДО начинать в процесс с начала, чтобы глянуть правильно ли выполнена разработка на бэк офисной системе или интеграционной шине.
В аджайл команде все участники процесса до начала разработки знают какие фичи они взяли в спринт и понимают как они должны работать с точки зрения конечного пользователя. В итоге мы приходим к тому, что время, которое необходимо затратить, прежде чем специалист приступит к тестированию, близко к нулю. Нам не нужно писать тест-кейсы (не спешите удивляться как же так, а как же передача знаний и тому подобное), мы сразу лезем в код и приступаем.
После окончания разработки мы получаем, что далее в тестирование задача идет уже проверенной и не содержащей критичных багов (как со стороны разработки, так и со стороны анализа). А что с некритичными? Такие баги встречаются, но крайне редко и обычно они завязаны на очень специфичные тестовые данные. Каждый такой кейс обрабатывается и заносится в вики команды. Но это еще не все плюшки, которые получаются на выходе!
При наличии в команде модульного тестирования автоматизатора (а он у нас есть!), на выходе мы также получаем автоматизированные модульные тесты! Это может быть набор тестов на каком либо выбранном тестовом фреймворке, либо набор скриптов, которые объединены в единый пак и вызываются по требованию/расписанию. Далее эти тесты включаются в некий регресс пак, который содержит в себе и автоматизированные модульные тесты и тесты отдела автоматизации (чуть позже мы рассмотрим, чем они отличаются) и прогоняется перед выводом задач спринта на бой. Что он нам дает думаю ясно и без моих пояснений.
Также допускается, подключение интеграционного тестированию параллельно с модульным тестированием. Пока команда тестирования через фронтовые системы дойдет до middle или back систем, тестирование будет выполнено уже более чем на 70-80%. Т.е. никаких простоев, связанных с необходимостью правки кода, у них не будет. Это что касается аджайл-команды.
Теперь посмотрим как обстоят дела в командах, которые работают по каскадному принципу. Схема с включенным модульным тестированием выглядит так:
Тут мы видим, что интеграционное тестирование начинается после окончания модульного. Это основное и принципиальное отличие от уже рассмотренного варианта. Пока задача не ушла с модульного тестирования, интеграционное тестирование не начинается. Готовятся тестовые сценарии, тестовые данные.
В остальном процесс не меняется, те же данные на выходе и те же плюшки, что и в agile командах.
Чуть выше я обещала поговорить о том, чем же отличаются автоматизированные модульные тесты, от автотестов команды автоматизации. Отличие очень простое и на поверхности. В 99% команда автоматизации автоматизирует не конкретную доработку, а процесс.
Рассмотрим на примере запроса информации по карте через интернет банк. Команда автоматизации пишет кейс на уровне фронта (логин/переход на вкладку/выбрали карту/нажали кнопку запросить баланс) Все мы знаем сколько по времени будет выполняться такой тест. А на выходе мы получим либо есть информация, либо ее нет. В очень редких случаях мы получим понимание в какой системе/каком функционале произошла ошибка.
Что с автоматизированными модульными тестами? Берем тот же пример. Тест написан на интеграционный уровень, которому на вход подается заполненный контракт (вытаскивается из фронтовой системы), далее происходит трансформация, вызов системы источника, обработка результатов и формирование ответа для системы приемника. В каждой точке стоит проверка. Осталось проверить, а точно ли все что надо вернула система источник? Далее вы знаете решение данной загадки. Тем самым мы получаем не один, а два теста, которые выполняются в несколько раз быстрее и дают исчерпывающие знания в случае ошибки. Не тратим время на анализ и разбор логов.
Как внедрить модульное тестирование?
Согласитесь, что простой целой команды на протяжении сколько-нибудь длительного времени это уже катастрофа? Поэтому основная польза от внедрения промежуточного этапа модульного тестирования в команду достаточно очевидна. Осталось только донести её до всех участников процесса и заинтересованных в конечном результате лиц.
Для начала нужно принять как данность, что не все понимают что вообще за зверь — модульное тестирование. Необходимо провести некоторую разъяснительною работу среди команды и доступно изложить всё то, что рассматривалось выше по поводу отличий такой команды от классических тестировщиков.
После подготовительной работы основная задача — добиться внедрения подхода с модульным тестированием хотя бы в тестовом режиме, выделив на это небольшую часть ресурсов.
После проведения нескольких показательных спринтов с новой формацией команды доказательствами эффективности такого подхода будут служить:
Заключение:
В итоге, по завершении всех вышеперечисленных операций по внедрению, модульное тестирование должно стать неотъемлемой частью процесса, которая позволит:
Обзор модульного и интеграционного тестирования Spring Boot
С чего начать мои усилия по тестированию?
Как Spring Boot может помочь мне в написании эффективных тестов?
Какие библиотеки мне использовать?
В этом блоге вы получите обзор того, как модульное и интеграционное тестирование работает со Spring Boot. Кроме того, вы узнаете, на каких функциях и библиотеках Spring следует сосредоточиться в первую очередь. Эта статья действует как агрегатор, и в нескольких местах вы найдете ссылки на другие статьи и руководства, которые более подробно объясняют концепции.
Модульное тестирование с помощью Spring Boot
Модульные тесты составляют основу вашей стратегии тестирования. Каждый проект Spring Boot, который вы запускаете с помощью Spring Initializr, имеет прочную основу для написания модульных тестов. Настраивать практически нечего, так как Spring Boot Starter Test включает в себя все необходимые строительные блоки.
Помимо включения и управления версией Spring Test, этот Spring Boot Starter включает и управляет версиями следующих библиотек:
Библиотеки проверки утверждений, такие как AssertJ, Hamcrest, JsonPath и т. Д.
В большинстве случаев ваши модульные тесты не нуждаются в какой-либо конкретной функции Spring Boot или Spring Test, поскольку они будут полагаться исключительно на JUnit и Mockito.
С помощью модульных тестов вы изолированно тестируете, например, свои *Service классы и имитируете каждого сотрудника тестируемого класса:
Как видно из раздела import тестового класса выше, Spring вообще не включается. Следовательно, вы можете применять методы и знания, полученные из модульного тестирования любого другого приложения Java.
Вот почему важно изучить основы JUnit 4/5 и Mockito, чтобы максимально использовать возможности модульного тестирования.
Для некоторых частей вашего приложения модульное тестирование не принесет особой пользы. Хорошими примерами для этого являются уровень персистентности или тестирование HTTP-клиента. Тестируя такие части вашего приложения, вы в конечном итоге почти копируете свою реализацию, поскольку вам приходится имитировать много взаимодействий с другими классами.
Лучшим подходом здесь является работа с нарезанным контекстом Spring, который можно легко автоматически настроить с помощью аннотаций теста Spring Boot.
Тесты фрагментов Spring Context
В дополнение к традиционным модульным тестам вы можете писать тесты с помощью Spring Boot, которые нацелены на определенные части (фрагменты) вашего приложения. TestContext Spring фреймворка вместе с Spring Boot адаптирует Spring контекст с достаточным количеством компонентов для конкретного теста.
Как назвать такие тесты? На мой взгляд, они не попадают на 100% в категорию модульных или интеграционных тестов. Некоторые разработчики называют их модульными тестами, потому что они тестируют, например, один контроллер изолированно. Другие разработчики относят их к интеграционным тестам, поскольку в них задействована поддержка Spring. Как бы вы их ни называли, убедитесь, что у вас есть общее понимание, по крайней мере, в вашей команде.
Все они автоматически настраивают фрагменты Spring TestContext и включают только компоненты Spring, относящиеся к тестированию определенной части вашего приложения. Я посвятил целую статью представлению наиболее распространенных из этих аннотаций и объяснению их использования.
Две наиболее важные аннотации (сначала изучите их):
Также доступны аннотации для других нишевых частей вашего приложения:
При их использовании важно понимать, какие компоненты входят в состав, TestContext а какие нет. Документация Javadoc каждой аннотации объясняет выполненную автоконфигурацию и цель.
Вы всегда можете расширить контекст автонастройки для своего теста, явно импортировав компоненты с помощью @Import или определив дополнительные компоненты Spring Beans, используя @TestConfiguration :
Вы можете найти дополнительные методы устранения потенциальных исключений NoSuchBeanDefinitionException, с которыми вы можете столкнуться при таких тестах, в этом сообщении в блоге.
Ловушка JUnit 4 и JUnit 5
Важно следить за импортом, особенно за @Test аннотацией:
С помощью JUnit 5 vintage-engine ваш набор тестов может содержать как тесты JUnit 3/4, так и JUnit Jupiter, но каждый тестовый класс может использовать только одну конкретную версию JUnit. Рассмотрите возможность миграции существующих тестов, чтобы использовать различные новые функции JUnit Jupiter (параметризованные тесты, распараллеливание, модель расширения и т. д.). Вы можете постепенно мигрировать свой набор тестов, так как вы можете запускать тесты JUnit 3/4 рядом с тестами JUnit 5.
Документация JUnit включает советы по миграции JUnit 4, а также имеются инструменты (JUnit Pioneer или эта функция IntelliJ) для автоматической миграции тестов (например, импорт или проверки утверждений).
После того, как вы мигрировали свой набор тестов на JUnit 5, важно исключить любое появление устаревшей версии JUnit. Не все в вашей команде могут постоянно обращать пристальное внимание на импорт библиотек тестирования. Чтобы избежать случайного смешивания разных версий JUnit, исключение их из вашего проекта помогает всегда выбирать правильный импорт:
Кроме Spring Boot Starter Test другие зависимости тестования также могут включать более старые версии JUnit:
Чтобы избежать (случайного) включения зависимостей JUnit 4 в будущем, вы можете использовать Maven Enforcer Plugin и определить его как запрещенную зависимость. Это приведет к сбою сборки, как только кто-то включит новую тестовую зависимость, которая транзитивно потянет JUnit 4.
Обратите внимание, что, начиная с Spring Boot 2.4.0, зависимость Spring Boot Starter Test больше не включает vintage-engine файл по умолчанию.
Интеграционные тесты с Spring Boot: @SpringBootTest
Вы можете переопределить это поведение, указав либо, DEFINE_PORT либо RANDOM_PORT :
Для интеграционных тестов, которые запускают встроенный контейнер сервлетов, вы можете затем внедрить порт своего приложения и получить к нему доступ извне, используя TestRestTemplate или WebTestClient :
Поскольку TestContext Spring фреймворка будет заполнять весь контекст приложения, вы должны убедиться, что присутствуют все зависимые компоненты инфраструктуры (например, база данных, очереди сообщений и т. д.).
Здесь в игру вступают Testcontainers. Testcontainers будет управлять жизненным циклом любого Docker контейнера для вашего теста:
Для ознакомления с Testcontainers рассмотрите следующие ресурсы:
Если ваше приложение обменивается данными с другими системами, вам нужно решение для имитации этого HTTP-взаимодействия. Это довольно часто бывает, когда вы, например, получаете данные из удаленного REST API или токенов доступа OAuth2 при запуске приложения. С помощью WireMock вы можете заглушить и подготовить HTTP-ответы для имитации удаленной системы.
Кроме того, TestContext Spring фреймворк имеет удобную функцию кеширования и повторного использования, а также уже запущенный контекст. Это может помочь сократить время сборки и значительно улучшить циклы обратной связи.
Сквозные тесты с Spring Boot
Целью сквозных (E2E) тестов является проверка системы с точки зрения пользователя. Сюда входят тесты для основных сценариев работы пользователя (например, размещение заказа или создание нового клиента). По сравнению с интеграционными тестами такие тесты обычно включают пользовательский интерфейс (если он есть).
Вы также можете выполнить тесты E2E для развернутой версии приложения, например, в среде dev или staging прежде чем приступить к развертыванию в рабочей среде.
Для приложений, которые используют рендеринг на стороне сервера (например, Thymeleaf) или автономной системы, когда серверная часть Spring Boot обслуживает интерфейс, вы можете использовать @SpringBootTest для этих тестов.
Следующий тест демонстрирует, как получить доступ и протестировать общедоступную страницу приложения Spring Boot с помощью Selenide:
Вы можете найти больше информации о Selenide в этом сообщении в блоге.
Для компонентов инфраструктуры, которые необходимо запустить для тестов E2E, Testcontainers играет большую роль. Если вам нужно запустить несколько Docker контейнеров, вам пригодится модуль Docker Compose из Testcontainers :
Резюме
Spring Boot предлагает отличную поддержку как для модульного, так и для интеграционного тестирования. Это делает тестирование первоклассным гражданином, поскольку каждый проект Spring Boot включает в себя Spring Boot Starter Test. Этот стартер подготовит ваш базовый набор инструментов для тестирования с необходимыми библиотеками тестирования.
Кроме того, аннотации Spring Boot test упрощают написание тестов для различных частей вашего приложения. Вы получите специально созданный Spring TestContext только с соответствующими Spring beans.
Чтобы познакомиться с модульными и интеграционными тестами для ваших проектов Spring Boot, рекомендуются следующие шаги:
Избегайте ловушки JUnit 4 и JUnit 5.
Ознакомьтесь с различными аннотациями тестирования Spring Boot, которые автоматически настраивают фрагменты контекста.
Узнайте, как Spring TestContext Caching может помочь сократить общее время выполнения вашего набора тестов.
Удачного модульного и интеграционного тестирования с помощью Spring Boot,
В этом руководстве вы получите некоторые практические рекомендации по написанию модульных тестов, чтобы создавать устойчивые и понятные тесты.
Почему именно модульные тесты?
Меньше времени на выполнение функциональных тестов
Функциональные тесты требуют большого количества ресурсов. Как правило, приходится открывать приложение и выполнять ряд действий, чтобы проверить ожидаемое поведение. Тест-инженеры не всегда знают, что это за действия, и им приходится обращаться к специалистам в этой области. Само тестирование может занимать несколько секунд, если это обычные изменения, или несколько минут для более масштабных изменений. Наконец, этот процесс необходимо повторять для каждого изменения, внесенного в систему.
Модульные тесты, с другой стороны, занимают миллисекунды, выполняются простым нажатием кнопки и не обязательно требуют знаний о всей системе в целом. Успешность прохождения теста зависит от средства выполнения теста, а не от пользователя.
Защита от регрессии
Дефекты регрессии вводятся при внесении изменений в приложение. Довольно часто тест-инженеры тестируют не только новую функцию, но и функции, существовавшие до этого, чтобы проверить, что эти функции по-прежнему работают должным образом.
С модульным тестированием можно повторно запускать весь набор тестов после каждой сборки или даже после изменения строки кода. Это дает вам уверенность, что ваш новый код не нарушил существующие функциональные возможности.
Исполняемая документация
Не всегда очевидно, что делает конкретный метод или как он себя ведет при определенных входных данных. Вы можете спросить себя: как поведет себя метод, если я передам ему пустую строку? А значение NULL?
Если у вас есть набор модульных тестов с понятными именами, каждый тест сможет четко объяснить, какими будут выходные данные для определенных входных данных. Кроме того, он сможет проверить, что это действительно работает.
Менее связанный код
Если код тесно связан, он плохо подходит для модульного тестирования. Без создания модульных тестов для кода это связывание может быть менее очевидным.
Когда вы пишете тесты для кода, вы естественным образом разделяете его, иначе его будет сложнее тестировать.
Характеристики хорошего модульного теста
Покрытие кода
Высокий процент покрытия кода зачастую связан с более высоким качеством кода. Однако само по себе измерение не может определить качество кода. Задание чрезмерно большого процента объема протестированного кода может снизить производительность. Представьте себе сложный проект с тысячами условных ветвей и представьте, что объем протестированного кода составляет 95 %. В настоящее время проект поддерживает объем протестированного кода в 90 %. Время, затрачиваемое на принятие всех пограничных вариантов в оставшихся 5 %, может оказаться очень значительным, а ценность предложения быстро сокращается.
Высокий процент объема протестированного кода не является индикатором успеха и не подразумевает высокое качество кода. Он представляет собой лишь объем кода, охваченного модульными тестами. Дополнительные сведения см. в статье Модульное тестирование объема протестированного кода.
Определимся с терминами
К сожалению, термин макет по отношению к тестированию часто употребляется неправильно. Следующие пункты определяют самые распространенные типы заполнителей при написании модульных тестов:
Заполнитель — это общий термин, который можно использовать для описания заглушки или макета объекта. Использование заглушки или макета зависит от контекста. Иными словами, заполнитель может быть заглушкой или макетом.
Макет. Макет объекта — это объект-заполнитель в системе, который решает, пройден ли модульный тест. Макет начинает существование как заполнитель, пока по нему не будет проведена проверка.
Заглушка — это управляемая замена существующей зависимости (или участника) в системе. С помощью заглушки можно протестировать код, не задействовав зависимость напрямую. По умолчанию заглушка выступает как заполнитель.
Рассмотрим следующий фрагмент кода:
Это будет пример заглушки, которая будет называться макетом. В данном случае это заглушка. Вы передаете Order, чтобы создать экземпляр Purchase (тестируемая система). Имя MockOrder также вводит в заблуждение, поскольку Order не является макетом.
Чтобы использовать его как макет, можно сделать нечто подобное:
В этом случае проверяется свойство заполнителя (выполняется проверка по нему), поэтому в приведенном выше фрагменте кода mockOrder является макетом.
Очень важно правильно разобраться в терминологии. Если вы будете называть заглушки макетами, другие разработчики не поймут ваших намерений.
В первую очередь следует помнить, что макеты отличаются от заглушек тем, что вы выполняете проверку по макету объекта, но не выполняете проверку по заглушке.
Рекомендации
Попробуйте не создавать зависимости в инфраструктуре при написании модульных тестов. Из-за этого тесты выполняются медленно и нестабильно. Зависимости следует использовать в интеграционных тестах. Чтобы избежать появления зависимостей в коде приложения, следуйте принципу явных зависимостей и используйте внедрение зависимостей. Вы также можете разместить модульные тесты в отдельном проекте, не содержащем интеграционных тестов. Это гарантирует отсутствие в проекте модульного теста ссылок на пакеты инфраструктуры или зависимостей от них.
Выбор имен для тестов
Имя теста должно состоять из трех частей:
Почему?
Тесты не просто проверяют работоспособность кода — они также предоставляют документацию. Просто посмотрев на набор модульных тестов, вы должны определить поведение кода, не глядя на сам код. Кроме того, если тест не пройден, вы сразу увидите, какие сценарии не соответствуют вашим ожиданиям.
Плохо:
Лучше:
Упорядочивание тестов
Упорядочивайте, действуйте, проверяйте — это общий шаблон для модульного тестирования. Как и предполагает название, он состоит из трех элементов.
Почему?
Удобочитаемость является одним из наиболее важных аспектов при написании тестов. Четкое разделение каждого из этих действий в тесте подчеркивает зависимости, необходимые для вызова кода, указывает, как вызывается ваш код и что вы пытаетесь проверить. Хотя некоторые шаги можно объединить и уменьшить размер теста, основной целью является удобочитаемость теста.
Плохо:
Лучше:
Пишите минималистичные тесты
Входные данные для модульного теста должны быть как можно проще, чтобы проверить поведение, которое вы тестируете в данный момент.
Почему?
Тесты, которые включают больше информации, чем требуется для прохождения, могут содержать больше ошибок и затруднять понимание намерения. При написании тестов необходимо сосредоточиться на поведении. Установка дополнительных свойств для моделей или использование ненулевых значений, когда это не требуется, только затрудняет проверку.
Плохо:
Лучше:
Избегайте «магических» строк
Именование переменных в модульных тестах так же важно, как именование переменных в рабочем коде, если не важнее. Модульные тесты не должны содержать «магических» строк.
Почему?
«Магические» строки могут запутать читателя тестов. Если строка выглядит необычно, у него может возникнуть вопрос, почему было выбрано определенное значение для параметра или возвращаемого значения. И ему придется рассматривать детали реализации, вместо того чтобы сосредоточиться на тесте.
При написании тестов старайтесь как можно яснее выразить свое намерение. В случае «магических» строк рекомендуется присваивать эти значения константам.
Плохо:
Лучше:
Избегайте логики в тестах
Почему?
При введении логики в набор тестов вероятность появления ошибок значительно возрастает. Меньше всего вам нужна ошибка в наборе тестов. Вы должны быть уверены, что тесты работают. Иначе вы не сможете им доверять. Если вы не доверяете тесту, он бесполезен. Если тест не пройден, вы должны знать, что с кодом действительно что-то не так и это нельзя игнорировать.
Если логика в тесте неизбежна, рекомендуется разбить тест на несколько тестов.
Плохо:
Лучше:
Выбирайте вспомогательные методы вместо установки и удаления
Почему?
В модульном тестировании Setup вызывается до всех модульных тестов в наборе. Хотя некоторые считают это полезным инструментом, в конечном итоге тесты сложно воспринимать. Каждый тест обычно имеет различные требования для запуска. К сожалению, Setup заставляет вас использовать одинаковые требования для всех тестов.
В xUnit удалены SetUp и TearDown в версии 2.x.
Плохо:
Лучше:
Избегайте нескольких действий
При написании тестов постарайтесь, чтобы каждый тест содержал только одно действие. Чтобы использовать только одно действие, можно применить следующие распространенные приемы.
Почему?
Для нескольких действий потребуются отдельные проверочные утверждения и нет гарантий, что все эти утверждения будут выполнены. В большинстве платформ модульного тестирования сбой одного из проверочных утверждений в модульном тесте приводит к тому, что все последующие тесты автоматически считаются непройденными. Это может вызывать путаницу, так как действующие функции будут казаться неработоспособными.
Плохо:
Лучше:
Проверяйте закрытые методы путем модульного тестирования открытых методов
В большинстве случаев вам не понадобится тестировать закрытые методы. Закрытые методы относятся к тонкостям реализации. Подумайте об этом так: закрытые методы никогда не существуют в изоляции. Рано или поздно у вас будет открытый метод, вызывающий закрытый метод в рамках его реализации. Вам следует думать о конечном результате открытого метода, который вызывает закрытый метод.
Рассмотрим следующий случай.
С этой точки зрения, если вы видите закрытый метод, найдите открытый метод и напишите тесты для него. Тот факт, что закрытый метод возвращает ожидаемый результат, вовсе не означает, что система, которая вызывает закрытый метод, использует этот результат правильно.
Используйте заглушки для статических ссылок
Один из принципов модульного тестирования — его полный контроль над тестируемой системой. Это может быть проблематичным, когда рабочий код вызывает статические ссылки (например, DateTime.Now ). Рассмотрим следующий код.
Как можно провести модульное тестирование этого кода? Вы можете попробовать следующий подход.
К сожалению, вы быстро поймете, что в тестах есть несколько проблем.
Для решения этих проблем вам потребуется ввести шов в рабочий код. Например, можно заключить код, который необходимо контролировать, в интерфейс, чтобы рабочий код зависел от этого интерфейса.