Мутационное тестирование
Мутационное тестирование — это способ оценки качества наших юнит-тестов.
Для мутационного тестирования существует инструмент Stryker NET, который автоматически выполнит мутации в коде, запустит тесты и составит подробный отчет с результатами.
Давайте разберемся, как это работает на примере.
У нас есть класс
В методе есть несколько ветвлений: если
Для начала установим Stryker NET. Для этого нужно выполнить команду
Далее запустим Stryker с помощью команды, из директории, где расположены юнит-тесты
После выполнения тестирования в консоли отобразится отчет — сколько мутантов было убито, а сколько выжило. Более подробный отчет в формате html появится в директории StrykerOutput тестового проекта.
Теперь рассмотрим, что такое мутанты и что значит «выжившие» и «убитые».
Мутант — это преднамеренная модификация кода, которую делает Stryker, чтобы проверить, изменятся ли результаты юнит-тестов.
В нашем примере мутант — это замена выражения
Stryker поддерживает несколько видов мутаций:
— эквивалентные (например, заменяет < на <=)
— арифметические (+ на -)
— строковые («text» на «»)
— логические (&& на ||)
и т. д., с полным списком вы можете ознакомиться в документации Stryker NET.
Если после изменения нашего кода юнит-тесты прошли успешно, значит, они недостаточно качественные, и мутант выжил.
После запуска мутационного тестирования выжило 5 мутантов. Добавим тестовые данные на граничные значения и повторно запустим мутационное тестирование.
После исправления эквивалентных мутантов у нас остались только строковые мутации, которые мы можем легко «убить» проверкой на текст exception message.
Таким образом, мутационное тестирование помогает находить слабые места в тестах и делает их надежнее. Оно заставляет проверять не только 'happy path', но и сложные граничные случаи, снижая вероятность багов в продакшене.
Доклад «Мутационное тестирование в .NET»
Testing Your Tests: Mutation Testing in C# with Stryker
Мой пост на LinkedIn, Medium
Мутационное тестирование — это способ оценки качества наших юнит-тестов.
Для мутационного тестирования существует инструмент Stryker NET, который автоматически выполнит мутации в коде, запустит тесты и составит подробный отчет с результатами.
Давайте разберемся, как это работает на примере.
У нас есть класс
PriceCalculator с методом Calculate, который считает цену с учетом скидки. Этот метод покрыт юнит-тестами.В методе есть несколько ветвлений: если
price меньше или равно 0, то мы выбросим исключение, а также если discountPercent меньше 0 или больше 100, то будет исключение.Для начала установим Stryker NET. Для этого нужно выполнить команду
dotnet tool install -g dotnet-stryker
Далее запустим Stryker с помощью команды, из директории, где расположены юнит-тесты
dotnet stryker
После выполнения тестирования в консоли отобразится отчет — сколько мутантов было убито, а сколько выжило. Более подробный отчет в формате html появится в директории StrykerOutput тестового проекта.
Теперь рассмотрим, что такое мутанты и что значит «выжившие» и «убитые».
Мутант — это преднамеренная модификация кода, которую делает Stryker, чтобы проверить, изменятся ли результаты юнит-тестов.
В нашем примере мутант — это замена выражения
price <= 0, например, на price < 0, после чего запускаются юнит-тесты.Stryker поддерживает несколько видов мутаций:
— эквивалентные (например, заменяет < на <=)
— арифметические (+ на -)
— строковые («text» на «»)
— логические (&& на ||)
и т. д., с полным списком вы можете ознакомиться в документации Stryker NET.
Если после изменения нашего кода юнит-тесты прошли успешно, значит, они недостаточно качественные, и мутант выжил.
После запуска мутационного тестирования выжило 5 мутантов. Добавим тестовые данные на граничные значения и повторно запустим мутационное тестирование.
[Fact]
public void InvalidPrice_ShouldThrowException()
{
Assert.Throws<ArgumentException>(()
=> PriceCalculator.CalculatePrice(0, 10));
// change price from -10 to 0
}
[Fact] // Added test for 0 and 100 discount
public void NoExceptionForZeroAnd100Discount()
{
var exceptionWhen0 = Record.Exception(() => PriceCalculator.CalculatePrice(100, 0));
var exceptionWhen100 = Record.Exception(() => PriceCalculator.CalculatePrice(100, 100));
Assert.Null(exceptionWhen0);
Assert.Null(exceptionWhen100);
}
После исправления эквивалентных мутантов у нас остались только строковые мутации, которые мы можем легко «убить» проверкой на текст exception message.
Таким образом, мутационное тестирование помогает находить слабые места в тестах и делает их надежнее. Оно заставляет проверять не только 'happy path', но и сложные граничные случаи, снижая вероятность багов в продакшене.
Доклад «Мутационное тестирование в .NET»
Testing Your Tests: Mutation Testing in C# with Stryker
Мой пост на LinkedIn, Medium
👍10
Полиморфная сериализация с System.Text.Json
Иногда из внешних сервисов приходят данные, содержащие один объект, но с разной структурой.
В нашем примере мы получаем JSON заказа Order, который содержит вложенный объект Client.
При этом Client может быть двух типов: Person или Company, каждый со своими уникальными полями.
Для определения типа клиента используется поле ClientType.
— Создать базовый класс
— Использовать атрибут
— Добавить
При этом в самих классах
Таким образом, при десериализации .NET автоматически выберет нужный класс, исключая необходимость ручного определения типа.
Атрибуты
Посты LinkedIn, Medium
Иногда из внешних сервисов приходят данные, содержащие один объект, но с разной структурой.
В нашем примере мы получаем JSON заказа Order, который содержит вложенный объект Client.
{
"Id": 1,
"Client": {
"ClientType": "Person",
"FullName": "John Doe"
}
}
{
"Id": 2,
"Client": {
"ClientType": "Company",
"CompanyName": "Big Tech Ltd"
}
}При этом Client может быть двух типов: Person или Company, каждый со своими уникальными полями.
Для определения типа клиента используется поле ClientType.
— Создать базовый класс
Client— Использовать атрибут
[JsonPolymorphic], указывая ClientType в качестве дескриминатора— Добавить
[JsonDerivedType] над базовым классом Client, указав производные классы PersonClient и CompanyClient и задав их соответствующие значения в ClientType (Person или Company).При этом в самих классах
PersonClient и CompanyClient нет необходимости вручную указывать поле ClientType — оно будет автоматически добавлено при сериализации и использоваться при десериализации для выбора нужного класса.Таким образом, при десериализации .NET автоматически выберет нужный класс, исключая необходимость ручного определения типа.
Атрибуты
[JsonPolymorphic] и [JsonDerivedType] доступны только с .NET 7. В более ранних версиях потребуется кастомный конвертер JsonConverter<T>.Посты LinkedIn, Medium
👍17
Как протестировать Middleware
В качестве примера возьмем простой кастомный Middleware под названием
Тестирование Middleware на первый взгляд может показаться сложным, так как требуется создавать HttpContext. Чтобы не зависеть от реальных HTTP-запросов и лишней инфраструктуры, будем использовать
Создадим тест с возникновением исключения
Для этого нам нужно замокать
Затем определим RequestDelegate, который выбросит исключение, и создадим экземпляр
После этого вызываем созданный Middleware, передавая ему
В секции проверки теста убеждаемся, что в
Тест без исключения
Также необходим тест, чтобы убедиться, что при отсутствии исключения код 500 не возвращается и логгер не вызывается. Для этого в RequestDelegate не выбрасываем исключение и проверяем, что StatusCode не равен 500, а метод логгера не был вызван.
Таким образом, мы можем проверить корректную работу нашего
Пост на LinkedIn, Medium
В качестве примера возьмем простой кастомный Middleware под названием
ExceptionHandlingMiddleware, который перехватывает исключения во время выполнения запроса, логирует их и возвращает HTTP-статус-код 500 (Internal Server Error).Тестирование Middleware на первый взгляд может показаться сложным, так как требуется создавать HttpContext. Чтобы не зависеть от реальных HTTP-запросов и лишней инфраструктуры, будем использовать
DefaultHttpContext.Создадим тест с возникновением исключения
Для этого нам нужно замокать
ILogger, я буду использовать NSubstitute. Затем определим RequestDelegate, который выбросит исключение, и создадим экземпляр
ExceptionHandlingMiddleware, передавая в конструктор наш делегат и мок логгера. После этого вызываем созданный Middleware, передавая ему
DefaultHttpContext, и ожидаем, что он обработает исключение. В секции проверки теста убеждаемся, что в
httpContext.Response.StatusCode установлено значение StatusCodes.Status500InternalServerError, а логгер был вызван один раз с соответствующими параметрами.Тест без исключения
Также необходим тест, чтобы убедиться, что при отсутствии исключения код 500 не возвращается и логгер не вызывается. Для этого в RequestDelegate не выбрасываем исключение и проверяем, что StatusCode не равен 500, а метод логгера не был вызван.
Таким образом, мы можем проверить корректную работу нашего
ExceptionHandlingMiddleware, не завися от реальных HTTP-запросов и внешней инфраструктуры.Пост на LinkedIn, Medium
👍7🔥2
Редактирование шаблона класса C# в Visual Studio 2022
В Visual Studio 2022 можно изменить шаблон нового класса. Я делаю это, так как предпочитаю, чтобы создаваемые классы были sealed и использовали file-scoped namespace. Для этого необходимо:
Отредактировать шаблон для классов:
Откройте файл
* если у вас версия Professional, замените часть пути Community на Professional.
После этого внесите необходимые изменения в шаблон (например, как показано на изображении или по вашим требованиям).
Отредактировать шаблон для классов в проектах ASP NET Core:
Откройте файл
и отредактируйте его аналогичным образом.
Настройка file-scoped namespace:
Если вы хотите использовать file-scoped namespace, добавьте в файл
* file-scoped namespaces доступны с .Net 6
Таким образом, вы сможете настроить шаблоны классов в Visual Studio 2022 в соответствии с вашими предпочтениями.
Пост на LinkedIn, Medium
В Visual Studio 2022 можно изменить шаблон нового класса. Я делаю это, так как предпочитаю, чтобы создаваемые классы были sealed и использовали file-scoped namespace. Для этого необходимо:
Отредактировать шаблон для классов:
Откройте файл
Class.cs, расположенный по следующему пути:C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\ItemTemplates\CSharp\Code\1033\Class* если у вас версия Professional, замените часть пути Community на Professional.
После этого внесите необходимые изменения в шаблон (например, как показано на изображении или по вашим требованиям).
Отредактировать шаблон для классов в проектах ASP NET Core:
Откройте файл
Class.cs, находящийся по пути:C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\ItemTemplates\AspNetCore\Code\1033\Classи отредактируйте его аналогичным образом.
Настройка file-scoped namespace:
Если вы хотите использовать file-scoped namespace, добавьте в файл
.editorconfig следующую запись:[*.cs]
csharp_style_namespace_declarations = file_scoped:suggestion
* file-scoped namespaces доступны с .Net 6
Таким образом, вы сможете настроить шаблоны классов в Visual Studio 2022 в соответствии с вашими предпочтениями.
Пост на LinkedIn, Medium
👍13
Как эффективно внедрить практику чистого кода в команде?
Несмотря на усилия по созданию codestyle конвенций часто возникают трудности с их практическим применением в команде.
Чтобы договоренности работали на практике, необходимо создать единое техническое пространство:
— Единые настройки профиля Code Cleanup: В Visual Studio или Rider важно использовать единые настройки профиля Code Cleanup, чтобы все участники команды могли автоматически применять одни и те же стандарты по форматированию и рефакторингу кода.
— Auto Cleanup в IDE: Убедитесь, что в вашей IDE настроен автоматический запуск Cleanup при сохранении файлов. Это поможет соблюдать единый стиль кодирования в реальном времени.
— Использование унифицированных шаблонов классов и интерфейсов: Используйте стандартизированные шаблоны для классов и интерфейсов. В предыдущем посте я рассказывал, как создать шаблон класса для Visual Studio.
— EditorConfig: Подготовьте и настройте файл
— Отсутствие Warnings: Договоритесь, что в проекте не допускаются warnings, что позволит поддерживать высокое качество кода с самого начала.
— Статические анализаторы: После реализации базовых настроек интегрируйте инструменты статического анализа кода, такие как
— Настройка подавлений (Suppression): Определите, какие правила анализаторов можно подавлять для упрощения работы, и добавьте соответствующие исключения в
Важно понимать, что эти техники не заменяют соглашения о коде, принятые в вашей команде. В самом начале этого соглашения четко определите его цели - улучшить качество кода, снизить риск ошибок и упростить процесс код-ревью.
Внедряя эти практики, вы не только повысите качество своего продукта, но и разовьете культуру ответственности за код, что положительно скажется на профессиональном росте всей команды.
Пост в LinkedIn, Medium
Несмотря на усилия по созданию codestyle конвенций часто возникают трудности с их практическим применением в команде.
Чтобы договоренности работали на практике, необходимо создать единое техническое пространство:
— Единые настройки профиля Code Cleanup: В Visual Studio или Rider важно использовать единые настройки профиля Code Cleanup, чтобы все участники команды могли автоматически применять одни и те же стандарты по форматированию и рефакторингу кода.
— Auto Cleanup в IDE: Убедитесь, что в вашей IDE настроен автоматический запуск Cleanup при сохранении файлов. Это поможет соблюдать единый стиль кодирования в реальном времени.
— Использование унифицированных шаблонов классов и интерфейсов: Используйте стандартизированные шаблоны для классов и интерфейсов. В предыдущем посте я рассказывал, как создать шаблон класса для Visual Studio.
— EditorConfig: Подготовьте и настройте файл
.editorconfig, который будет задавать базовые правила форматирования кода для всей команды.— Отсутствие Warnings: Договоритесь, что в проекте не допускаются warnings, что позволит поддерживать высокое качество кода с самого начала.
— Статические анализаторы: После реализации базовых настроек интегрируйте инструменты статического анализа кода, такие как
Roslynator, Meziantou, SonarQube. Они помогут обнаруживать проблемы до этапа ревью.— Настройка подавлений (Suppression): Определите, какие правила анализаторов можно подавлять для упрощения работы, и добавьте соответствующие исключения в
.editorconfig.Важно понимать, что эти техники не заменяют соглашения о коде, принятые в вашей команде. В самом начале этого соглашения четко определите его цели - улучшить качество кода, снизить риск ошибок и упростить процесс код-ревью.
Внедряя эти практики, вы не только повысите качество своего продукта, но и разовьете культуру ответственности за код, что положительно скажется на профессиональном росте всей команды.
Пост в LinkedIn, Medium
👍11
Удобный способ сгенерировать файл editorconfig по вашим требованиям
В предыдущем посте я рассказывал, как создать единое техническое пространство для соблюдения codestyle-конвенций внутри команды.
Это пространство включает настройки IDE (например, настройки правил code cleanup, автоматическую очистку кода при сохранении файлов), набор статических анализаторов и файл
Сегодня я хочу поделиться удобным способом генерации файла
Для начала откройте диалоговое окно Options через меню
В открывшемся окне вы увидите список правил стиля кода с указанием их предпочтений (Preference) и уровня важности (Severity), а также визуальное отображение примеров настроек для каждого правила. Именно благодаря такому наглядному представлению я предпочитаю этот метод генерации
После выбора необходимых предпочтений для правил, установите для каждого из них соответствующий уровень важности (Severity).
Например, в слайде, где показаны настройки для
После настройки правил достаточно нажать Generate
Пост на LinkedIn
В предыдущем посте я рассказывал, как создать единое техническое пространство для соблюдения codestyle-конвенций внутри команды.
Это пространство включает настройки IDE (например, настройки правил code cleanup, автоматическую очистку кода при сохранении файлов), набор статических анализаторов и файл
editorconfig, по которому работает функция очистки IDE и в котором задаются исключения для правил статических анализаторов.Сегодня я хочу поделиться удобным способом генерации файла
editorconfig для C# в Visual Studio 2022.Для начала откройте диалоговое окно Options через меню
Tools, затем выберите Text-Editor–C#–Code Style–General. В открывшемся окне вы увидите список правил стиля кода с указанием их предпочтений (Preference) и уровня важности (Severity), а также визуальное отображение примеров настроек для каждого правила. Именно благодаря такому наглядному представлению я предпочитаю этот метод генерации
editorconfig.После выбора необходимых предпочтений для правил, установите для каждого из них соответствующий уровень важности (Severity).
Например, в слайде, где показаны настройки для
Code block preferences, для параметра Preference braces выбрано значение Yes с уровнем важности Warning. Это означает, что если в проекте будет использована конструкция if без фигурных скобок, IDE выдаст предупреждение.После настройки правил достаточно нажать Generate
.editorconfig file from settings и сохранить файл в корне проекта.Пост на LinkedIn
👍8
Спустя два месяца мой пул-реквест наконец принят и замержен в документацию .NET. Надеюсь, что уже в ближайшее время на learn.microsoft.com появится мой небольшой гайд по генерации юнит-тестов с использованием Copilot и Visual Studio 2022. А сейчас в разработке находится ещё один гайд
🔥35
Как навести порядок в зависимостях пакетов в проектах?
Часто в legacy-проектах возникает хаос с зависимостями: неиспользуемые библиотеки, пакеты, подключенные не к тем проектам, или использование транзитивных зависимостей вместо прямых.
Visual Studio позволяет быстро удалить неиспользуемые пакеты.
Для этого:
— Кликните правой кнопкой мыши по проекту.
— Выберите пункт
Однако после этой операции могут возникнуть ошибки. Почему?
Первая причина
Пакет подключен к одному проекту, но используется в другом (зависимом).
Например:
— Проекты
— Пакет prometheus-net.AspNetCore ошибочно подключен к
— После команды Remove Unused References пакет prometheus-net.AspNetCore удаляется из
— Решение: доустановите нужный пакет непосредственно в
Вторая причина
Используются косвенные зависимости.
Например:
— В проекте зарегистрирован пакет prometheus-net.AspNetCore, хотя прямое использование Prometheus отсутствует.
— Однако класс
— После удаления неиспользуемых ссылок появятся ошибки, так как
— Решение — подключение подходящего целевого пакета, например, Microsoft.AspNetCore.Http.Abstractions.
Таким образом, правильный подход к управлению зависимостями не только очищает проект от лишних библиотек, но и делает кодовую базу более прозрачной и управляемой.
Пост на LinkedIn, Medium
Часто в legacy-проектах возникает хаос с зависимостями: неиспользуемые библиотеки, пакеты, подключенные не к тем проектам, или использование транзитивных зависимостей вместо прямых.
Visual Studio позволяет быстро удалить неиспользуемые пакеты.
Для этого:
— Кликните правой кнопкой мыши по проекту.
— Выберите пункт
Remove Unused References...Однако после этой операции могут возникнуть ошибки. Почему?
Первая причина
Пакет подключен к одному проекту, но используется в другом (зависимом).
Например:
— Проекты
API и Core, где API зависит от Core.— Пакет prometheus-net.AspNetCore ошибочно подключен к
Core, хотя используется только в API.— После команды Remove Unused References пакет prometheus-net.AspNetCore удаляется из
Core, что вызовет ошибки в проекте API.— Решение: доустановите нужный пакет непосредственно в
API (например, через комбинацию Alt+Enter → Install package).Вторая причина
Используются косвенные зависимости.
Например:
— В проекте зарегистрирован пакет prometheus-net.AspNetCore, хотя прямое использование Prometheus отсутствует.
— Однако класс
HttpRequest использует зависимости этого пакета.— После удаления неиспользуемых ссылок появятся ошибки, так как
HttpRequest потеряет необходимые зависимости.— Решение — подключение подходящего целевого пакета, например, Microsoft.AspNetCore.Http.Abstractions.
Таким образом, правильный подход к управлению зависимостями не только очищает проект от лишних библиотек, но и делает кодовую базу более прозрачной и управляемой.
Пост на LinkedIn, Medium
👍8🔥1
Насыщенная была неделя. Мои гайды наконец появились на официальном сайте Learn Microsoft. Опубликовался гайд по мутационному тестированию (https://learn.microsoft.com/en-us/dotnet/core/testing/mutation-testing) и руководство по генерации тестов с помощью GitHub Copilot в Visual Studio 2022 (https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-copilot).
Встретился с Данилом @danny_v3 автор канала "Логгирую разработку". На его канале можно найти записи реальных собеседований. Это его первый визит в Алматы, и я не мог упустить шанс показать горы.
Ребята с подкаста RadioDotNet порадовали меня классным мерчем (см. фото) – такие подарки отправляются тем, кто поддерживает подкаст на Boosty. Очень рекомендую послушать их передачи, особенно плейлист RadioDotNet, обсуждение книг "System Design Interview" и "Architecture for Containerized .NET Applications".
Встретился с Данилом @danny_v3 автор канала "Логгирую разработку". На его канале можно найти записи реальных собеседований. Это его первый визит в Алматы, и я не мог упустить шанс показать горы.
Ребята с подкаста RadioDotNet порадовали меня классным мерчем (см. фото) – такие подарки отправляются тем, кто поддерживает подкаст на Boosty. Очень рекомендую послушать их передачи, особенно плейлист RadioDotNet, обсуждение книг "System Design Interview" и "Architecture for Containerized .NET Applications".
🔥12👍5