.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
День 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