C# (C Sharp) programming
18.1K subscribers
958 photos
51 videos
8 files
766 links
По всем вопросам- @notxxx1

Реестр РКН: https://clck.ru/3Fk3kb

#VRHSZ
Download Telegram
Интеграционные тесты прямо в CI/CD - максимум уверенности в коде 🚀

Я запускаю интеграционные тесты прямо внутри CI/CD пайплайна.
Так я проверяю не абстракции и моки, а реальное поведение системы.

Всё это возможно благодаря:
- Docker
- Testcontainers

Идея простая:
ты поднимаешь реальные внешние сервисы как контейнеры и используешь их прямо в тестах.

В примере поднимаются:
- PostgreSQL
- Redis
- Keycloak

Контейнеры:
- автоматически стартуют перед тестами
- доступны из кода приложения
- уничтожаются после выполнения тестов

Почему это работает лучше моков:
- тесты максимально близки к продакшену
- одинаковое поведение локально и в CI
- сразу ловятся проблемы с конфигурацией, миграциями, auth
- меньше сюрпризов после деплоя

Особенно полезно для:
- backend-сервисов
- микросервисной архитектуры
- систем с авторизацией и внешними зависимостями
- .NET-приложений с серьёзной бизнес-логикой

Если хочешь вывести интеграционное тестирование в .NET на новый уровень — Testcontainers стоит попробовать обязательно.

#dotnet, #testing
🐳 «Используй Testcontainers вместо in-memory» - это только половина правды

Все уже выучили: EF Core InMemory provider - не интеграционный тест.

Он не ловит:

- баги в LINQ-трансляции
- ограничения БД
- коллации
- реальные типы колонок
- поведение конкретного SQL-провайдера

Окей, заменили на реальный PostgreSQL через Testcontainers. Победа? Не совсем.

Вот что начинается дальше.

1. Вы получили «медленное враньё» вместо «быстрого»

Поднимать контейнер на каждый тест-класс - быстрый способ превратить CI из 30 секунд в 8 минут.

Нормальный вариант:

- один контейнер на всю тестовую сессию
- изоляция данных между тестами через Respawn
- без пересоздания базы и контейнера каждый раз

Respawn чистит таблицы с учётом графа foreign keys за миллисекунды.

2. Транзакционный откат ≠ реальный сценарий

Трюк «обернули тест в транзакцию и откатили» красиво выглядит, но ломается, когда в коде есть:

- свои транзакции
- несколько SaveChanges
- фоновые операции
- поведение, завязанное на commit

В итоге тестируется сценарий, которого в проде нет.

3. Самая коварная ловушка - общий DbContext

Если тест и код используют один экземпляр DbContext, EF может вернуть данные из change tracker, а не из базы.

Тест зелёный, но он врёт: реальный SQL-запрос мог вообще не выполниться.

Между Act и Assert стоит чистить трекер:


Db.ChangeTracker.Clear();


4. Бонус, который теряют 90% команд - тест миграций

Реальная БД позволяет прогнать EF-миграции на чистой схеме.

Если миграция падает или схема разъехалась с моделью, вы узнаёте об этом в CI, а не в проде в пятницу вечером.

Пример базового подхода:


public class IntegrationTestBase : IAsyncLifetime
{
private static readonly PostgreSqlContainer _db =
new PostgreSqlBuilder()
.WithImage("postgres:16-alpine")
.Build();

private Respawner _respawner = null!;
protected AppDbContext Db = null!;

public async Task InitializeAsync()
{
await _db.StartAsync();

var options = new DbContextOptionsBuilder<AppDbContext>()
.UseNpgsql(_db.GetConnectionString())
.Options;

Db = new AppDbContext(options);

// Реальные миграции - заодно проверяем, что они накатываются
await Db.Database.MigrateAsync();

await using var conn = new NpgsqlConnection(_db.GetConnectionString());
await conn.OpenAsync();

_respawner = await Respawner.CreateAsync(conn, new RespawnerOptions
{
DbAdapter = DbAdapter.Postgres,
SchemasToInclude = ["public"]
});
}

// Сброс данных перед каждым тестом - без пересоздания контейнера
protected async Task ResetAsync()
{
await using var conn = new NpgsqlConnection(_db.GetConnectionString());
await conn.OpenAsync();

await _respawner.ResetAsync(conn);

// Иначе тест может читать из кеша, а не из БД
Db.ChangeTracker.Clear();
}

public Task DisposeAsync() => Task.CompletedTask;
}


Testcontainers - это не галочка «best practice», а смена философии.

Без нормальной изоляции данных вы просто пересели с быстрого вранья на медленное.

А как вы изолируете состояние БД между интеграционными тестами - Respawn, транзакции или пересоздание контейнера?

#dotnet #csharp #testing #efcore
💡 C#: заставьте архитектуру ломать CI, если кто-то нарушил правила

Компилятор C# отлично проверяет типы, но ему всё равно, что Application внезапно начал зависеть от Infrastructure.

Code Review тоже не гарантирует, что такое заметят.

Для этого можно использовать Architecture Tests — тесты, которые проверяют не бизнес-логику, а структуру проекта.

Например, через NetArchTest.Rules:


[Fact]
public void Application_Should_Not_Depend_On_Infrastructure()
{
var result = Types
.InAssembly(ApplicationAssembly)
.ShouldNot()
.HaveDependencyOn("MyApp.Infrastructure")
.GetResult();

Assert.True(result.IsSuccessful);
}


Теперь случайный:


Application → Infrastructure


сломает CI так же, как обычный упавший unit-тест.

Можно зафиксировать и naming convention:


[Fact]
public void Handlers_Should_End_With_Handler()
{
var result = Types
.InAssembly(ApplicationAssembly)
.That()
.ImplementInterface(typeof(ICommandHandler<>))
.Should()
.HaveNameEndingWith("Handler")
.GetResult();

Assert.True(result.IsSuccessful);
}


Или заставить сервисы быть sealed:


Types
.InAssembly(ApplicationAssembly)
.That()
.HaveNameEndingWith("Service")
.Should()
.BeSealed();


Особенно полезно это становится в больших Clean Architecture / DDD / Modular Monolith проектах.

Вместо документа:


«Application не должен зависеть от Infrastructure»


получаем исполняемое правило:


нарушил архитектуру → тест упал → PR не прошёл


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

#CSharp #DotNet #Architecture #Testing