DotNet developer blog C# | .Net
803 subscribers
85 photos
36 links
Blog about C# .NET development. Almost all posts are published in english on my linkedin page https://www.linkedin.com/in/yegor-sychev/
Download Telegram
Мутационное тестирование

Мутационное тестирование — это способ оценки качества наших юнит-тестов.
Для мутационного тестирования существует инструмент 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.
{
"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 под названием 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. Для этого необходимо:

Отредактировать шаблон для классов:

Откройте файл 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: Подготовьте и настройте файл .editorconfig, который будет задавать базовые правила форматирования кода для всей команды.

— Отсутствие Warnings: Договоритесь, что в проекте не допускаются warnings, что позволит поддерживать высокое качество кода с самого начала.

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

— Настройка подавлений (Suppression): Определите, какие правила анализаторов можно подавлять для упрощения работы, и добавьте соответствующие исключения в .editorconfig.

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

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

Пост в LinkedIn, Medium
👍11
Удобный способ сгенерировать файл editorconfig по вашим требованиям

В предыдущем посте я рассказывал, как создать единое техническое пространство для соблюдения 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 позволяет быстро удалить неиспользуемые пакеты.
Для этого:

— Кликните правой кнопкой мыши по проекту.
— Выберите пункт 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".
🔥12👍5