.NET Разработчик
6.7K subscribers
470 photos
4 videos
14 files
2.33K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2052. #Testing
Введение в WebApplicationFactory. Окончание
Начало

Теперь добавим тест. Для простых сценариев можно использовать HttpClient.
public class MyApiTests :
IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient client;

public MyApiTests(
WebApplicationFactory<Program> factory)
{
client = factory.CreateClient();
}

[Fact]
public async Task
PassingNameShouldReturnWelcomeMessage()
{
var resp =
await client.PostAsJsonAsync("/", new
{
Name = "Jon Smith"
});

Assert.True(resp.IsSuccessStatusCode);
var content =
await resp.Content.ReadAsStringAsync();
Assert.Equal("Hello Jon Smith!", content);
}
}

Почему мы передали анонимный объект, а не HelloRequest (он ведь публичный)? Несколько причин:
1. Он публичный, но не чтобы использовать в тестах. Помните, мы являемся пользователем API. Пользователь не знает внутреннего представления наших доменных объектов.
2. Тесты не должны пострадать, если мы поменяем доменную модель.
3. Мы также тестируем сериализацию и десериализацию.
Поэтому анонимный объект идеален.

Конфигурация
Есть много настроек WebApplicationFactory, которые можно сделать в конструкторе. Рассмотрим основные:
public MyApiTests(
WebApplicationFactory<Program> factory)
{
this.factory = factory.WithWebHostBuilder(
builder =>
{
// Изменить настройки из application.json
builder.UseSetting(
"ConnectionString",
"file=:memory:");

// Если в appsettings.json объект:
// MyObject {
// MyProp: 123
// }
// Используем нотацию ":"
builder.UseSetting("MyObject:MyProp", 234);

// Изменить среду и загружать
// настройки из appsettings.tests.json
builder.UseEnvironment("tests");

// Перенастроить сервисы
builder.ConfigureServices(
services => …
);
});
}


Источник: https://steven-giesel.com/blogPost/cd62475b-2c7d-4ce2-bd97-9670f91ebac8/introduction-to-webapplicationfactory
👍16
День 2205. #Testing
Нагрузочное Тестирование с Помощью K6 в Windows. Начало

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

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

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

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

Демо проект
Создадим простой проект .NET API. Одна конечная точка, /randombook, которая возвращает информацию о случайной книге, хранящейся в БД в памяти:
int requests = 0;
int concurrency = 0;
object _lock = new();
app.MapGet("/randombook", async (CancellationToken ct) =>
{
Book? book = default;
var delayMs = Random.Shared.Next(10, 10000);
try
{
lock (_lock)
{
requests++;
concurrency++;
app.Logger.LogInformation(
@"Request {Count}.
Concurrent Executions {Executions}.
Delay: {DelayMs}ms",
requests, concurrency, delayMs);
}

using var ctx = new ApiContext();
await Task.Delay(delayMs, ct);
if (ct.IsCancellationRequested)
{
app.Logger.LogWarning("Cancelled");
throw new OperationCanceledException();
}
var books = await ctx.Books.ToArrayAsync();
book = Random.Shared
.GetItems(books, 1).First();
}
catch (Exception ex)
{
app.Logger.LogError(ex, "Error ");
return Results.Problem(ex.Message);
}
finally
{
lock (_lock)
{
concurrency--;
}
}

return TypedResults.Ok(book);
});

Здесь добавлены:
- случайная задержка delayMs, эмулирующая запрос из БД;
- потокобезопасный счётчик параллельных операций concurrency;
- логирование сообщений внутри lock для избежания проблем параллелизма.
Это, конечно, не идеальное решение, но оно подойдёт для демонстрации.

Далее посмотрим, как провести нагрузочное тестирование этого API.

Продолжение следует…

Источник:
https://www.code4it.dev/blog/k6-load-testing/
1👍18
День 2206. #ЗаметкиНаПолях #Testing
Нагрузочное Тестирование с Помощью K6 в Windows. Продолжение

Начало

Запуск K6 в Windows
С помощью K6 вы можете запустить нагрузочные тесты, определив конечную точку для вызова, количество запросов в минуту и некоторые другие настройки.

Этот бесплатный инструмент можно установить с помощью Winget:
winget install k6 --source winget

Проверить правильность установки можно через командную строку (не Powershell):
k6 --version

Теперь можно инициализировать инструмент:
k6 new

Команда сгенерирует файл script.js, в котором надо будет настроить конфигурацию тестов. Например:
import http from "k6/http"
import { sleep } from "k6"

export const options = {
vus: 10,
duration: "30s",
}

export default function () {
http.get("https://localhost:7123/randombook")
sleep(1)
}

Здесь:
- vus: 10 - виртуальные пользователи, симулирующие параллельные входящие запросы;
- duration: "30s" – общее время теста;
- http.get("https://…") - основная функция, вызывающая конечную точку и считающая ответы, метрики, тайминг и т.п.;
- sleep(1) – время паузы между итерациями.

То есть, в течение 30 секунд k6 будет посылать до 10 параллельных запросов, потом ждать 1 секунду, и повторять. После он истечения времени теста, он даст ещё 30 секунд приложению, чтобы завершить текущие запросы.

Для запуска убедитесь, что API запущен, и выполните следующую команду:
k6 run script.js


В консоли API мы увидим логи запросов:
[15:19:51 INF] Request 1. Concurrent Executions 1. Delay: 7124ms
[15:20:02 INF] Request 2. Concurrent Executions 1. Delay: 4981ms

[15:20:27 INF] Request 57. Concurrent Executions 10. Delay: 7655ms

А в консоли k6 отчёт вроде представленного на рисунке выше.

Окончание следует…

Источник:
https://www.code4it.dev/blog/k6-load-testing/
👍12
День 2207. #ЗаметкиНаПолях #Testing
Нагрузочное Тестирование с Помощью K6 в Windows. Окончание

Начало
Продолжение

Отчёт
Вернёмся к отчёту, показанному на картинке в предыдущем посте. Либо, установив 2 переменные окружения, вы можете получить более визуально приятный отчёт в виде HTML документа, показанного на рисунке выше.
set K6_WEB_DASHBOARD=true
set K6_WEB_DASHBOARD_EXPORT=html-report.html
k6 run script.js

В отчёте множество значений, названия которых в основном говорят сами за себя:
- data_received и data_sent - размер отправленных и полученных данных;
- продолжительность и ответы HTTP-запросов (http_req_duration, http_req_sending, http_reqs);
- информация о фазах HTTP-соединения, например http_req_tls_handshaking;
- конфигурации K6 (iterations, vus и vus_max).
Вы можете увидеть среднее значение, минимальное и максимальное значение, а также некоторые процентили для большинства значений.

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

HTTP-методы
Мы использовали только метод GET, но можно использовать все доступные HTTP-методы с помощью соответствующей функции Javascript:
- get() – метод GET,
- post() - метод POST,
- put() - метод PUT,
- del() - метод DELETE.

Стадии
Вы можете определить несколько стадий тестирования, например:
export const options = {
stages: [
{ duration: "30s", target: 20 },
{ duration: "1m30s", target: 10 },
{ duration: "20s", target: 0 },
],
}

Здесь определены 3 стадии:
1. 30 сек – нагрузка в 20 виртуальных пользователей,
2. 1м 30 сек – 10,
3. 20 сек – время на завершение оставшихся запросов.

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

Сценарий — это элемент JSON, в котором вы определяете аргументы, такие как продолжительность, количество пользователей (как мы рассмотрели выше), а также переменные среды, время старта и т.д. Определив сценарий, вы можете запустить тесты на той же конечной точке, но с использованием разных поведений. Например, создать один сценарий для постепенного роста пользователей, a другой - для резкого взлёта их количества.

Источник: https://www.code4it.dev/blog/k6-load-testing/
👍9
День 2282. #Testing
Тестирование Характеристик
Большинство типов тестирования проверяют правильность. Мы тестируем код, чтобы увидеть, что он делает то, что мы хотим, чтобы он делал. Это предполагает, что мы знаем, что он должен делать. А что, если мы этого не знаем?

К сожалению, это бывает часто. Нужно вносить изменения в код, но мы недостаточно знаем о том, что он делает. Вот небольшой пример:
public class Parser
{
public static string FormatText(string text)
{
var result = new StringBuilder();
for (int n = 0; n < text.Length; ++n)
{
int c = text[n];
if (c == '<')
{
while (n < text.Length && text[n] != '/' && text[n] != '>')
n++;
if (n < text.Length && text[n] == '/')
n += 4;
else
n++;
}
if (n < text.Length)
result.Append(text[n]);
}
return result.ToString();
}
}

Что делает этот код? Кажется, удаляет HTML-теги из текста, но логика странная и, скорее всего, неправильная. Этот крошечный пример показывает, как сложно читать плохо написанный код. Мы можем использовать тестирование. Но вместо того, чтобы пытаться выяснить, является ли код правильным, мы можем попытаться охарактеризовать его поведение, чтобы понять, что он на самом деле делает. Начнем с простого теста. Создадим тест и назовём его «x». «X», потому что мы не знаем, что будет делать метод FormatText. И мы даже не зададим ожидаемого значения, т.к. на данный момент мы не знаем, каким будет поведение:
[TestMethod]
public void x()
{
Assert.AreEqual(null, Parser.FormatText("text"));
}

Тест упадёт, но мы хотя бы узнали, что метод выдаст в виде результата. Теперь мы можем поставить результат вместо ожидаемого значения и заставить тест проходить. А также зададим тесту имя, которое отражает наше понимание того, что делает код:
[TestMethod]
public void DoesNotChangePlainText()
{
Assert.AreEqual("text", Parser.FormatText("text"));
}

Мы не разобрались в коде, а пишем тесты. Какую ценность они могут иметь? Большую. Когда мы пишем тесты характеристик, мы накапливаем знания о том, что на самом деле делает код. Это особенно полезно, когда мы хотим его отрефакторить. Мы можем запустить наши тесты и сразу узнать, изменили ли мы поведение. Вот другой тест. Он проходит:
[TestMethod]
public void RemovesTextBetweenAngleBrackets()
{
Assert.AreEqual("", Parser.FormatText("<>"));
}

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

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

Мы можем рассматривать тесты характеристик как описания того, что у нас есть, а не как утверждения о правильности. Можно периодически пересматривать тесты, чтобы ужесточить их условия, когда мы решаем, каким должно быть поведение. Самое сложное — разорвать зависимости вокруг фрагмента кода, чтобы иметь возможность проверить его в тестовой среде. Как только вы это сделаете, останется только поинтересоваться, что будет делать код в определённых условиях и запустить тест, чтобы найти фактическое значение. Часто вы будете пересматривать названия тестов, поскольку будете больше понимать код, который проверяете. Начните с теста с именем «x».

Источник: https://michaelfeathers.silvrback.com/characterization-testing
👍14
День 2361. #Testing #BestPractices
Лучшие Практики Интеграционного Тестирования с Testcontainers. Начало

Интеграционные тесты с Testcontainers — мощный инструмент, но их поддержка может быстро превратиться в кошмар. Сегодня рассмотрим шаблоны, которые делают тесты Testcontainers надёжными, быстрыми и простыми в поддержке.

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

Testcontainers решает эту проблему, разворачивая настоящие Docker-контейнеры для ваших зависимостей. Тесты выполняются с использованием реальных PostgreSQL, Redis или любого другого сервиса, используемого в рабочей среде. После завершения тестов контейнеры уничтожаются, каждый раз позволяя вам начинать с чистого листа.

Всё происходит через API Docker. Testcontainers управляет всем жизненным циклом: извлечение образов, запуск контейнеров, ожидание готовности и очистка. Тестовому коду нужно лишь знать, как подключиться.

1. Подготовка
Убедитесь, что у вас есть необходимые пакеты:
Install-Package Microsoft.AspNetCore.Mvc.Testing
Install-Package Testcontainers.PostgreSql
Install-Package Testcontainers.Redis

Пакеты TestContainers существуют для множества сервисов.

2. Создание
Вот так можно создать контейнеры для PostgreSql и Redis:
var _pg = new PostgreSqlBuilder()
.WithImage("postgres:17")
.WithDatabase("mydb")
.WithUsername("postgres")
.WithPassword("postgres")
.Build();

var _redis = new RedisBuilder()
.WithImage("redis:latest")
.Build();


3. Использование
Чтобы запускать и останавливать контейнеры в тестах, нужно реализовать IAsyncLifetime в вашей WebApplicationFactory:
public sealed class IntegrationWebAppFactory :
WebApplicationFactory<Program>, IAsyncLifetime
{
public async Task InitializeAsync()
{
await _pg.StartAsync();
await _redis.StartAsync();
// Старт других зависимостей
}

public async Task DisposeAsync()
{
await _pg.StopAsync();
await _redis.StopAsync();
// Остановка других зависимостей
}
}

Это гарантирует готовность контейнеров до запуска тестов и их очистку после них. Т.е. отсутствие остаточного состояния Docker или состояний гонки.
Совет: закрепите версии образов (например, postgres:17), чтобы избежать сюрпризов от изменений версий зависимостей.

4. Передача конфигурации в приложение
Testcontainers назначает динамические порты. Не пишите жёсткие строки подключения в коде. Вместо этого внедряйте значения через WebApplicationFactory.ConfigureWebHost:
protected override void 
ConfigureWebHost(IWebHostBuilder bldr)
{
bldr.UseSetting("ConnectionStrings:Database",
_pg.GetConnectionString());
bldr.UseSetting("ConnectionStrings:Redis",
_redis.GetConnectionString());
}

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

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

Окончание следует…

Источник:
https://www.milanjovanovic.tech/blog/testcontainers-best-practices-dotnet-integration-testing
👍13
День 2362. #Testing #BestPractices
Лучшие Практики Интеграционного Тестирования с Testcontainers. Окончание

Начало

5. Совместное использование настроек с фикстурами xUnit
Фикстура — это общий контекст для тестов, позволяющий настроить дорогостоящие ресурсы, такие как БД или брокеры сообщений, один раз и использовать их повторно в нескольких тестах. Выбор между фикстурами классов и коллекций влияет как на производительность тестов, так и на изоляцию.

Фикстура класса — один контейнер на каждый тестовый класс.
Используйте, когда тесты изменяют глобальное состояние или когда отладка тестовых взаимодействий становится затруднительной. Применяйте, когда требуется полная изоляция между тестовыми классами (это медленнее, но безопаснее).
public class AddItemToCartTests : 
IClassFixture<IntegrationWebAppFactory>
{
private IntegrationWebAppFactory _factory;

public AddItemToCartTests(
IntegrationWebAppFactory factory)
{
_factory = factory;
}

[Fact]
public async Task ShouldFail_WhenNotEnoughQuantity()
{ … }
}


Фикстура коллекции — один контейнер, общий для нескольких тестовых классов.
Используйте, когда тесты не изменяют общее состояние или когда вы можете надёжно выполнить очистку между тестами. Т.е. когда тестовые классы не мешают друг другу (это быстрее, но требует дисциплины).
[CollectionDefinition(nameof(IntegrationCollection))]
public sealed class IntegrationCollection :
ICollectionFixture<IntegrationWebAppFactory>
{
}

// Применение
[Collection(nameof(IntegrationCollection))]
public class AddItemToCartTests :
IntegrationTestFixture
{
public AddItemToCartTests(
IntegrationWebAppFactory factory)
: base(factory) { }

[Fact]
public async Task Should_Fail_WhenNotEnoughQuantity()
{ … }
}

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

6. Вспомогательные методы для аутентификации и очистки
Фикстура может предоставлять вспомогательные методы для упрощения написания тестов:
public async Task<HttpClient> 
CreateAuthenticatedClientAsync() { … }

protected async Task CleanupDBAsync() { … }

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

7. Написание поддерживаемых интеграционных тестов
При правильной настройке инфраструктуры ваши тесты должны быть сосредоточены на бизнес-логике. Сложность контейнеров должна быть скрыта за грамотно спроектированными базовыми классами и вспомогательными методами. В тестах вы не должны заботиться о правильной имитаций Postgres или Redis, а должны тестировать реальное поведение.

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

Начните с простого: выберите один интеграционный тест, который в настоящее время использует моки или БД в памяти, и преобразуйте его для использования Testcontainers. Вы сразу заметите разницу в уверенности, когда тест пройдёт успешно. Затем постепенно расширяйте его, чтобы охватить критически важные бизнес-процессы.

Источник: https://www.milanjovanovic.tech/blog/testcontainers-best-practices-dotnet-integration-testing
👍5
День 2411. #SystemDesign101 #Testing
9 Видов Тестирования API


1. Дымовое (Smoke) тестирование
Проводится после завершения разработки API. Просто проверяется работоспособность API и отсутствие сбоев.

2. Функциональное тестирование
Составление плана тестирования на основе функциональных требований и сравнение результатов с ожидаемыми.

3. Интеграционное тестирование
Тестирование объединяет несколько вызовов API для выполнения сквозных тестов. Тестируются внутрисервисные коммуникации и передача данных.

4. Регрессионное тестирование
Тестирование гарантирует, что исправления ошибок или новые функции не нарушат текущее поведение API.

5. Нагрузочное тестирование
Тестирование производительности приложений путем моделирования различных нагрузок. Затем мы можем рассчитать пропускную способность приложения.

6. Стресс-тестирование
Мы намеренно создаем высокие нагрузки на API и проверяем его работоспособность.

7. Тестирование безопасности
Тестирование API на предмет всех возможных внешних угроз.

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

9. Фаззинг-тестирование
Этот метод внедряет недействительные или неожиданные входные данные в API и пытается вызвать сбой в его работе. Таким образом, выявляются уязвимости API.

Источник: https://blog.bytebytego.com/
👍10
День 2610. #Testing
TUnit – Новый Фреймворк Тестирования в .NET. Начало

xUnit, NUnit и MSTest многие годы хорошо служили командам для модульного, интеграционного и даже приёмочного тестирования. Однако по мере развития .NET с появлением новых версий среды выполнения, AOT-компиляции, требований кроссплатформенности, всё более крупных наборов тестов и более быстрых конвейеров CI/CD ограничения устаревших фреймворков стали более очевидными. TUnit стремится решить многие из этих проблем, или, по крайней мере, заявляет об этом. В этой серии рассмотрим, что может предложить TUnit.

Проблема существующих фреймворков
1. Накладные расходы на рефлексию
xUnit, NUnit и MSTest в значительной степени полагаются на рефлексию для обнаружения тестов, создания экземпляров фикстур и вызова методов тестирования. В больших наборах тестов накладные расходы на рефлексию могут значительно влиять на время выполнения.
Обнаружение тестов на основе рефлексии происходит во время выполнения, заставляя разработчиков ждать, прежде чем тесты начнут выполняться. Это особенно раздражает в конвейерах CI/CD.

2. Асинхронный подход был второстепенным
async/await был добавлен в существующие фреймворки тестирования. В результате иногда получаются неуклюжие утверждения, непоследовательная обработка асинхронных операций и не всегда понятный код.

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

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

Заявления TUnit
Философия проектирования TUnit основана на скорости, современности, гибкости, простоте и лёгкости внедрения.

Ключевые концепции:
1. Производительность прежде всего — использование генерации кода во время компиляции вместо рефлексии, с параллельным выполнением тестов, включённым по умолчанию.
2. Совместимость с современным .NET — полная поддержка .NET 8 и более поздних версий, нативный AOT, тримминг и новые возможности среды выполнения.
3. Управление и гибкость — рабочие процессы на основе атрибутов и конфигурации, пользовательские источники данных, расширяемое поведение, цепочки зависимостей и тонкое управление планированием.
4. Широкая применимость — подходит не только для модульных тестов, но и для интеграционных, приёмочных и даже сквозных сценариев (включая поддержку таких библиотек, как Playwright).
5. Прочие отличительные особенности — асинхронные утверждения, Fluent API, различные источники данных (аргументы, методы, классы, матрицы), диагностика во время компиляции, скорость выполнения на 30% выше xUnit.

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

Далее подробнее рассмотрим функции, которые предлагает TUnit.

Продолжение следует…

Источник:
https://trailheadtechnology.com/tunit-the-new-sheriff-in-town-for-net-testing/
👍10
День 2611. #Testing
TUnit – Новый Фреймворк Тестирования в .NET. Продолжение

Начало

Ключевые особенности TUnit

1. Нативная поддержка асинхронности
Утверждения ожидаются, обработчики инициализации асинхронны, и весь жизненный цикл теста учитывает, что современная .NET является асинхронной платформой.
xUnit:
[Fact]
public async Task TestAsync()
{
var result = await GetDataAsync();
Assert.Equal(5, result.Items.Count);
}


TUnit:
[Test]
public async Task TestAsync()
{
var result = await GetDataAsync();
await Assert.That(result.Items)
.Count()
.IsEqualTo(5);
}

Каждое утверждение ожидается. Это позволяет TUnit:
- Точно захватывать асинхронный контекст,
- Связывать утверждения с fluent-синтаксисом,
- Обрабатывать асинхронные утверждения без специального или пользовательского синтаксиса,
- Предоставлять более понятные и информативные сообщения об ошибках с полным контекстом выполнения.

2. Генерация кода и проверка во время компиляции
TUnit использует генераторы кода C# для обнаружения и компиляции тестов во время сборки, полностью исключая необходимость рефлексии во время выполнения. Такой подход позволяет выявлять многие ошибки во время компиляции, а не во время выполнения, и значительно сокращает время запуска тестов. Это также улучшает общее качество отладки, поскольку сгенерированный код обеспечивает более понятные пути выполнения и более предсказуемое поведение по сравнению с традиционными фреймворками, основанными на рефлексии.
[Test]
[Arguments(1, 2, 3)]
[Arguments(4, 5, 9)]
public async Task Add_WithVariousInputs(
int a, int b, int expected)
{
var result = Add(a, b);
await Assert.That(result).IsEqualTo(expected);
}

// Упрощённый сгенерированный код:
// [CompilerGenerated]
// public async Task Add_WithVariousInputs_1()
// {
// var result = Add(1, 2);
// await Assert.That(result).IsEqualTo(3);
// }
// [CompilerGenerated]
// public async Task Add_WithVariousInputs_2() { … }


3. Модель параллелизации
// Тесты по умолчанию распараллеливаются 
[Test]
[Parallel] // Явно распараллелить
public async Task Test1() { }

// Последовательное выполнение при необходимости
[Test]
[Sequential]
public async Task Test2() { }

// Общие ресурсы для параллельных тестов [ClassDataSource<DatabaseFixture>(Shared = SharedType.PerClass)]
public class DatabaseTests
{
[Test]
public async Task Test1(DatabaseFixture db) { }

[Test]
public async Task Test2(DatabaseFixture db) { }
}

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

Продолжение следует…

Источник:
https://trailheadtechnology.com/tunit-the-new-sheriff-in-town-for-net-testing/
👍10
День 2612. #Testing
TUnit – Новый Фреймворк Тестирования в .NET. Продолжение

Начало
Особенности 1-3

4. Передача данных в тесты
1) Аргументы уровня компиляции
[Test]
[Arguments(1, 2, 3)]
[Arguments(4, 5, 9)]
public async Task Addition(
int a, int b, int expected)
{
await Assert.That(a + b).IsEqualTo(expected);
}


2) Метод-источник (динамические данные)
[Test]
[MethodDataSource(nameof(TestCases))]
public async Task WithDynamicData(
int value, string expected)
{
var result = ProcessValue(value);
await Assert.That(result).IsEqualTo(expected);
}

public static
IEnumerable<(int, string)> TestCases()
{
yield return (1, "one");
yield return (2, "two");
yield return (3, "three");
}


3) Матричные тесты (комбинаторные)
[Test]
public async Task MatrixCombinations(
[Matrix(1, 2, 3)] int x,
[Matrix("a", "b")] string y,
[Matrix(true, false)] bool z)
{
// 3 × 2 × 2 = 12 комбинаций
await Assert.That(Validate(x, y, z)).IsTrue();
}


4) Класс-источник
[ClassDataSource<UserFixture>(Shared = SharedType.PerClass)]
public class UserServiceTests
{
[Test]
public async Task CreateUser(UserFixture fixture)
{
var user = await
fixture.Service.CreateAsync("John");
await Assert.That(user.Name).IsEqualTo("John");
}
}

public class UserFixture
{
public IUserService Service { get; }
= new UserService();
}

Как видите, TUnit поддерживает практически все функции, предлагаемые существующими фреймворками, часто усовершенствованные и модернизированные.

5. Жизненный цикл тестов и фикстур
public class TestLifecycleExample
{
private DatabaseConnection _conn;

// Перед каждым тестом
[Before(Test)]
public async Task SetupTest()
{
_conn = new DatabaseConnection();
await _conn.OpenAsync();
}

[Test]
public async Task DatabaseQuery()
{
var result =
await _conn.QueryAsync("SELECT * FROM Users");
await Assert.That(result).IsNotNull();
}

// После каждого теста
[After(Test)]
public async Task CleanupTest()
{
await _conn.CloseAsync();
_conn?.Dispose();
}

// Однажды на тестовый класс
[Before(Class)]
public static async Task SetupClass()
{
// Инициализация общих ресурсов
}

[After(Class)]
public static async Task CleanupClass()
{
// Очистка общих ресурсов
}

// Однажды на сборку
[Before(Assembly)]
public static async Task SetupAssembly()
{
// Инициализация данных уровня приложения
}
}


Окончание следует…

Источник:
https://trailheadtechnology.com/tunit-the-new-sheriff-in-town-for-net-testing/
👍6
День 2613. #Testing
TUnit – Новый Фреймворк Тестирования в .NET. Окончание

Начало
Особенности 1-3
Особенности 4-5

6. Нативный Fluent API
TUnit поддерживает цепочку fluent утверждений «из коробки», без необходимости установки каких-либо дополнительных пакетов или настройки.
[Test]
public async Task FluentAssertions()
{
var user = new { Name = "Alice",
Age = 30, Email = "alice@example.com" };

// Цепочка утверждений
await Assert.That(user.Name)
.IsNotNull()
.IsNotEmpty()
.StartsWith("Al");

// 'And' для логической группировки
await Assert.That(user.Age)
.IsGreaterThan(18)
.And.IsLessThan(65);

// Несколько утверждений в области
// (сообщение сразу о всех неудачах, а не о первой)
using var scope = Assert.Multiple();
await Assert.That(user.Name).IsEqualTo("Alice");
await Assert.That(user.Age).IsEqualTo(30);
await Assert.That(user.Email).IsNotNull();
}


7. Нативная поддержка внедрения зависимостей
Для использования внедрения зависимостей не требуется сложная настройка, TUnit поддерживает его «из коробки».
[Test]
public async Task WithDependencyInjection(
IUserService userService, ILogger logger)
{
var user = await userService.GetUserAsync(1);
logger.LogInformation($"Loaded user: {user.Name}");

await Assert.That(user).IsNotNull();
}

// Конфигурация через ServiceCollection
public void ConfigureServices(
IServiceCollection services)
{
services.AddScoped<IUserService, UserService>();
services.AddLogging();
}


Итого
Использовать ли TUnit?
TUnit - шаг вперёд в тестировании .NET. Существующие фреймворки остаются надёжными, стабильными и зрелыми, но они отражают проектные решения, принятые задолго до AOT, генераторов кода или современных ожиданий производительности.

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

Источник: https://trailheadtechnology.com/tunit-the-new-sheriff-in-town-for-net-testing/
👍11