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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2043. #Testing
Автоматизированные Тесты. Начало
Т
есты важны для обеспечения качества приложения. Существует множество видов тестов: модульные, интеграционные, функциональные, дымовые и т.п. Но некоторые тесты не вписываются ни в одну категорию, и создатели придают им другое значение, создавая их.

Тесты — это компромиссы:
- Уверенность: Гарантирует ли тест, что приложение работает так, как ожидалось?
- Продолжительность: Сколько времени требуется для выполнения теста?
- Надёжность: Выдаёт ли тест случайные сбои?
- Усилия по написанию: Легко ли реализовать тестовый сценарий?
- Стоимость обслуживания: Сколько времени нужно на обновление теста при изменении кода?

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

Теперь я принял новую стратегию, которая оказалась очень эффективной для разработанного мной приложения. Мои текущие практики включают:
1. Тестирование в основном публичного API, поскольку внутренние компоненты неявно тестируются и включаются в метрики покрытия кода. Публичный API – не обязательно классы или методы, а может быть точками входа приложения или конечными точками HTTP.
2. Минимизация использования моков, резервирование их для особых случаев, таких как взаимодействие клиентов с внешними сервисами.
3. Использование строгого статического анализа, чтобы избежать написания тестов для того, что может быть обнаружено во время компиляции.
4. Больше утверждений в коде (например, Debug.Assert).

Это даёт несколько преимуществ:
1. Меньше тестов нужно поддерживать.
2. Пользовательские сценарии чётко определены и протестированы.
3. Не нужно переписывать тесты при рефакторинге кода или изменениях в реализации, пока API не изменился.
4. Возможно безопасно рефакторить код, обеспечивая неизменное поведение.
5. Помогает выявлять устаревшие сегменты кода.

Тестирование публичного API
Публичный API — это то, как ваше приложение отображается для пользователей. Публичный API может отличаться в зависимости от типа проекта:
- Библиотека — классы и методы, доступные пользователям библиотеки.
- Веб-API — конечные точки HTTP.
- Приложение CLI — аргументы командной строки.

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

Например, если вы пишете приложение ASP.NET Core, не тестируйте класс Controller напрямую. Используйте WebApplicationFactory для настройки и запуска сервера, затем отправляйте запрос и проверяйте ответ. Так вы тестируете приложение в целом, включая маршрутизацию, привязку модели, фильтры, промежуточное ПО, внедрение зависимостей и т. д. Также возможен рефакторинг на Minimal API или изменение промежуточного ПО без обновления тестов.

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

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

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

Источник:
https://www.meziantou.net/automated-tests.htm
Автор оригинала: Gérald Barré
👍14👎1
День 2044. #Testing
Автоматизированные Тесты. Продолжение

Начало

Подразумевает ли термин «модульное тестирование» тестирование каждой единицы реализации? Не совсем. Кент Бек, автор книги «Экстремальное программирование: разработка через тестирование», признал, что название «модульный тест», возможно, не лучший выбор. В этом контексте «модуль» относится к определённому поведению, которое может включать взаимодействие нескольких модулей реализации. Некоторые даже определяют «модуль» как целый пакет или библиотеку, и эта точка зрения также верна. Некоторые используют термины «изолированные» и «коммуникабельные» тесты, чтобы различать тесты, использующие и не использующие моки зависимостей.

Пишите код помогающий писать тесты
Написание теста должно быть простым, а тестовый код — понятным. Важно создать тестовую инфраструктуру, которая упрощает процесс написания тестов. Часто используют класс TestContext, содержащий общий код для написания тестов. Например, он может содержать код запуска приложения, отправки запроса, подтверждения ответа, регистрации мока и т. д. Так вы сможете сосредоточиться на написании теста, а не на шаблонном коде:
// Arrange
using var ctx = new TestContext();
ctx.ReplaceService<IService, MockImplementation>();
ctx.SetConfigurationFile("""{ "path: "/tmp" }""");
ctx.SeedDatabase(db =>
db.Users.Add(new User { Username = "user" }));
var user = ctx.SetCurrentUser("user");

// Act
var response = await ctx.Get("/user");

// Assert
Assert.AreEqual(response, """
Id: 1
UserName: user
""");


Нужно ли имитировать зависимости?
Моки (Moq) — способ протестировать приложение без использования реальных зависимостей. Поэтому, используя моки, вы тестируете не реальное приложение, а его разновидность. Важно максимально приблизить эту разновидность к реальности. Может быть сложно поддерживать тесты с моками. Сервисы, которые вы имитируете, могут изменить поведение, и вы можете не заметить этого, поскольку не используете их напрямую. Также приходится писать много кода для настройки тестов.

Разработчики, как правило, используют слишком много моков. Нужно ли имитировать файловую систему? Это не так-то просто, и у многих неправильные предположения о ней. Например, в Windows она чувствительна к регистру, некоторые имена файлов или символы в них недопустимы. В случае кроссплатформенного приложения может потребоваться протестировать поведение, специфичное для ОС. Почему бы просто не писать во временный каталог? То же самое касается БД. Можно использовать Docker для запуска БД, чтобы протестировать SQL и ограничения базы?

В большинстве случаев нужно имитировать внешние сервисы, которые не находятся под вашим контролем. Рассмотрите возможность использования фактических сервисов, когда это возможно. Конечно, желательно, чтобы тесты были изолированы. Здесь есть много стратегий. Например, если нужно читать или записывать файлы, можно создать временный каталог для каждого теста. Если вы запускаете БД в контейнере, можно использовать разные имена баз для изоляции тестов. Можно положиться на эмуляторы, предоставляемые поставщиками облачных услуг, для локального запуска некоторых зависимостей. Так вы будете уверены, что код работает, как ожидалось, и не нужно писать сложные моки. Убедитесь, что приложение можно настроить, например, легко подставлять разные строки соединений или папки для хранения данных приложения.

Полезные инструменты для избежания моков
- TestContainers или .NET Aspire. Если запуск Docker-контейнера медленный, можно переиспользовать его между несколькими тестовыми запусками. .NET Aspire также может предоставлять ресурсы в облаке (Azure, AWS и т. д.).
- Эмуляторы облачных сервисов. Например, Azure предоставляет эмулятор для хранилища.

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

Источник:
https://www.meziantou.net/automated-tests.htm
👍16👎1
День 2045. #Testing
Автоматизированные Тесты. Продолжение

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

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

Я стараюсь избегать использования фреймворков-имитаторов, а создавать тестовые двойники вручную. Тестовые двойники могут более точно имитировать реальные сценарии, чем фреймворки-имитаторы. Они могут сохранять состояние между несколькими вызовами методов, что может быть сложно с фреймворками-имитаторами. Может потребоваться убедиться, что мок вызывается в правильном порядке (например, Save перед Get – хотя это можно сделать), или что вызывается правильное количество раз.

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

Это может показаться более трудоёмким, но эти усилия не существенны. Обычно вы не имеете дело с чрезмерным количеством фиктивных классов, и можете повторно использовать обобщённую реализацию в различных тестах. Так вы избегаете дублирования блоков кода для настройки в каждом тесте.
// Используем фреймворк-имитатор FakeItEasy
// Что, если сервис вызовет GetAsync до SaveAsync?
var service = A.Fake<IService>();
A.CallTo(() => service.SaveAsync()).Returns(true);
A.CallTo(() => service.GetAsync()).Returns(["value"]);

var sut = new MyClass(service);
sut.DoSomething();

Сравните с:
// Используем собственный двойник 
var service = new StubService();
service.AddItem("value"); // Добавляем данные

var sut = new MyClass(service);
sut.DoSomething();


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

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

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

Источник:
https://www.meziantou.net/automated-tests.htm
Автор оригинала: Gérald Barré
👍7👎1
День 2046. #Testing
Автоматизированные Тесты. Окончание

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

Пишите больше утверждений в коде
Хотя утверждения (Assert) в тестах являются наиболее распространённым способом проверки поведения кода, можно писать утверждения и в коде. Например, использовать Debug.Assert для проверки состояния приложения. Это может быть полезно для проверки некоторых предположений, которые у вас есть относительно кода. Одним из преимуществ является то, что ошибка будет возникать на ранней стадии и также при отладке приложения, а не только при запуске тестов.

Утверждения также могут улучшить читаемость кода. Вы можете увидеть ожидаемое поведение непосредственно в коде, что может быть полезно при написании сложного кода или когда вы хотите проверить определённое поведение:
public void DoSomething()
{
Debug.Assert(_service != null,
"Этого быть не должно!");
_service.DoSomething();
}


Тестовые фреймворки
Используйте тот, который вам больше нравится: xUnit, NUnit и MSTests очень похожи по функциям. Синтаксис может отличаться, но концепции одинаковы. Для утверждений можно использовать встроенные или библиотеку вроде FluentAssertions.

Покрытие
Не стоит стремиться к 100% покрытию тестами. Вот некоторые проблемы с этой метрикой:
- 100% покрытие означает, что ошибок нет? Нет, это просто означает, что весь код покрыт тестами. И чаще всего только ваш код, но не зависимости.
- Сколько времени вы потратите на написание тестов, чтобы покрыть последние 10% кода? Стоит ли оно того?
- Нужно ли покрывать все пути в коде? Например, если есть метод, который выдаёт ArgumentNullException, нужно ли его тестировать? Добавляет ли это больше уверенности в коде или это просто пустая трата времени?

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

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

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

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

Некоторые инструменты CI могут обнаружить нестабильность теста. Например, Azure DevOps может это делать.

См. также:
-
Мутационное тестирование с помощью Stryker
-
Пишите Тесты Быстрее с Approval Tests

Источник: https://www.meziantou.net/automated-tests.htm
👍5
День 2051. #Testing
Введение в WebApplicationFactory. Начало

Сегодня рассмотрим, что такое WebApplicationFactory и как она помогает в тестировании.

Мотивация
Все мы знаем про юнит-тесты, интеграционные тесты и End-to-end тесты. Чёткого разграничения нет. Спросите 10 разработчиков, и они дадут вам 10 разных определений. Но несомненно, что есть код, если модули кода, и их как-то надо тестировать.

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

Преимущества
1. Если контракт поменяется (как в WebAPI), тест не пройдёт.
2. Вы тестируете всю цепочку и пишете тесты с точки зрения конечного пользователя (не полагаясь на технические детали).
3. Вы получаете хороший обзор ваших функций, таким образом создавая «живую документацию» своего кода.

WebApplicationFactory
Это упрощённый сервер в памяти, который запускает ваш WebAPI ASP.NET Core. Она применит все настройки приложения (включая application.json), DI и будет работать с реальной БД (если не настроено иначе).
Вот простейший минимальный API для примера:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/", (HelloRequest request) =>
$"Hello {request.Name}!");

app.Run();

И простая запись для запроса:
public record HelloRequest(string Name); 


Настроим тестовый проект (xUnit, nUnit, MSTest – какой хотите).
Добавим NuGet-пакет Microsoft.AspNetCore.Mvc.Testing и ссылку с тестового проекта на основной.

Теперь можно написать код настройки тестов. В xUnit используется конструктор:
public class MyApiTests :
IClassFixture<WebApplicationFactory<Program>>
{
public void MyApiTests(
WebApplicationFactory<Program> factory)
{
}
}

Здесь IClassFixture<WebApplicationFactory<Program>> используется, чтобы указать xUnit передать WebApplicationFactory<Program> в конструктор.

Замечания:
1. Называйте классы и методы тестов как модули и их функции, которые вы хотите протестировать. Это важно, если вы хотите иметь живую документацию внутри своего кода.
2. Этот код не скомпилируется. Причина в том, что класс Program является внутренним (internal). В минимальных API больше нет метода Main, компилятор создаёт его за вас. Но теперь там используется модификатор internal. Одним из возможных исправлений было бы добавление InternalsVisibleTo, но это влечёт за собой много исправлений. Тогда тестовые методы также должны быть внутренними. Но внутренние методы не могут быть выполнены тестовой средой. Есть более простой «хак»: с помощью partial класса сделать класс Program публичным. Добавьте в конец файла Program.cs:
public partial class Program
{
}

Не идеально для минимальных API, но просто.

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

Источник:
https://steven-giesel.com/blogPost/cd62475b-2c7d-4ce2-bd97-9670f91ebac8/introduction-to-webapplicationfactory
👍18
День 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