Виды тестов и пирамида в .NET

↑ 2.4 Тестирование в .NET — основы · → Следующая

Обязательно~4 мин чтенияУровень: middle

Зачем это на собесе

«Какие бывают тесты и что вы тестируете в первую очередь?» — вопрос на зрелость. Ждут не перечисления, а логики выбора границы теста по риску.

Объяснение

УровеньЧто проверяетСкоростьХрупкостьДоля
Unitодну единицу поведения без I/Oмиллисекундынизкаябольшая
Integrationвзаимодействие с реальной БД, очередью, HTTP-слоемсекундысредняясредняя
Contractсовместимость API между сервисами (Pact, OpenAPI)быстронизкаяпо необходимости
End-to-endпользовательский сценарий через всю системудесятки секундвысокаямалая
Нагрузочные, безопасность, mutationнефункциональные свойствадолго—точечно

Классическая пирамида: много быстрых unit-тестов внизу, меньше интеграционных, совсем мало E2E. Для сервисов на .NET часто работает «соты/бриллиант»: основная ценность в интеграционных тестах через WebApplicationFactory + Testcontainers, потому что бизнес-логика тонкая, а риски в SQL, сериализации и конфигурации.

Что считать unit

Не «один класс», а «одна единица поведения». Тест, который знает о приватных вызовах и порядке обращений к мокам, ломается при рефакторинге и не защищает поведение.

Структура теста: Arrange–Act–Assert

[Fact]
public async Task Returns_not_found_for_missing_order()
{
    // Arrange
    var sut = new GetOrderHandler(new InMemoryOrders());
 
    // Act
    var result = await sut.Handle(new GetOrder(Guid.NewGuid()), CancellationToken.None);
 
    // Assert
    result.Should().BeOfType<NotFound>();
}

Интеграционный тест API

public class OrdersApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    public OrdersApiTests(WebApplicationFactory<Program> f) => _client = f.CreateClient();
 
    [Fact]
    public async Task Get_unknown_order_returns_404()
    {
        var resp = await _client.GetAsync($"/orders/{Guid.NewGuid()}");
        Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
    }
}

Нюансы и подводные камни

  • Тесты деталей реализации (проверка вызовов приватных зависимостей) мешают рефакторингу.
  • Coverage не равен качеству: строка выполнена, но ничего не проверено.
  • Мок БД вместо настоящей скрывает ошибки SQL и миграций; для этого нужен Testcontainers.
  • Тесты не должны зависеть друг от друга, порядка запуска и текущего времени.
  • Flaky-тесты подрывают доверие к CI: чините или удаляйте, не перезапускайте по кругу.

Практика

  1. Возьмите свой сервис и разложите существующие тесты по уровням пирамиды.
  2. Напишите один интеграционный тест на эндпоинт через WebApplicationFactory.
  3. Сформулируйте, какие риски не покрыты, и выберите вид теста под каждый.

Вопросы с ответами

Связанные темы