Тестирование в C#: Unit, Integration, E2E — Полное руководство для .NET

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Тестирование в C#: от Unit до E2E — как построить качественную архитектуру проверок в .NET

Качество программного обеспечения напрямую зависит от системы тестирования. В экосистеме C# и .NET существует устоявшаяся пирамида тестирования: модульные (unit), интеграционные (integration) и сквозные (end-to-end) тесты. В этой статье мы разберём каждый уровень на практических примерах, покажем, какие библиотеки использовать, и дадим готовые шаблоны кода.

1. Unit-тестирование: основа стабильности кода

Unit-тесты проверяют работу отдельных методов, классов или компонентов изолированно от внешних зависимостей (БД, API, файловой системы). Основная цель — быстро найти регрессию при рефакторинге.

Инструменты для C#

  • NUnit — классический фреймворк с атрибутами [Test], [TestCase].
  • xUnit — современный фреймворк от создателей NUnit, предпочтителен в новых проектах.
  • Moq — библиотека для создания mock-объектов.
  • FluentAssertions — для читаемых assert-ов.

Пример unit-теста на xUnit с Moq

Допустим, у нас есть сервис расчёта скидки, который обращается к репозиторию пользователей:

public interface IUserRepository
{
    bool IsPremiumUser(int userId);
}

public class DiscountService
{
    private readonly IUserRepository _userRepo;
    public DiscountService(IUserRepository userRepo) => _userRepo = userRepo;

    public decimal CalculateDiscount(int userId, decimal amount)
    {
        if (_userRepo.IsPremiumUser(userId))
            return amount * 0.9m; // 10% скидка
        return amount;
    }
}

// Тест
public class DiscountServiceTests
{
    [Fact]
    public void CalculateDiscount_ForPremiumUser_Returns90Percent()
    {
        // Arrange
        var mockRepo = new Mock<IUserRepository>();
        mockRepo.Setup(r => r.IsPremiumUser(It.IsAny<int>())).Returns(true);
        var service = new DiscountService(mockRepo.Object);

        // Act
        var result = service.CalculateDiscount(1, 100m);

        // Assert
        Assert.Equal(90m, result);
    }
}

Совет: Используйте [Theory] и [InlineData] в xUnit для параметризованных тестов — это сокращает дублирование кода.

2. Интеграционное тестирование: проверка связки компонентов

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

Библиотеки и подходы

  • TestContainers — запускает Docker-контейнеры (PostgreSQL, SQL Server, Redis) прямо из тестов.
  • WebApplicationFactory (из Microsoft.AspNetCore.Mvc.Testing) — создаёт тестовый хост ASP.NET Core.
  • Respawn — очищает БД между тестами, не пересоздавая контейнер.

Пример интеграционного теста с TestContainers и WebApplicationFactory

Проверим, что наш API корректно сохраняет пользователя в PostgreSQL:

public class UserApiIntegrationTests : IClassFixture<TestContainersFixture>
{
    private readonly HttpClient _client;
    private readonly TestContainersFixture _fixture;

    public UserApiIntegrationTests(TestContainersFixture fixture)
    {
        _fixture = fixture;
        var factory = new WebApplicationFactory<Program>()
            .WithWebHostBuilder(builder =>
            {
                builder.ConfigureTestServices(services =>
                {
                    // Заменяем реальную строку подключения на контейнер
                    services.RemoveAll<DbContextOptions<AppDbContext>>();
                    services.AddDbContext<AppDbContext>(options =>
                        options.UseNpgsql(fixture.ConnectionString));
                });
            });
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task CreateUser_Returns201()
    {
        // Act
        var response = await _client.PostAsJsonAsync("/api/users", new { Name = "Test" });

        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
    }
}

// Фикстура для TestContainers
public class TestContainersFixture : IAsyncLifetime
{
    private readonly PostgreSqlContainer _container = new PostgreSqlBuilder().Build();
    public string ConnectionString => _container.GetConnectionString();

    public async Task InitializeAsync() => await _container.StartAsync();
    public async Task DisposeAsync() => await _container.DisposeAsync();
}

Важно: Интеграционные тесты медленнее unit-тестов, поэтому их количество должно быть разумным — покрывайте только критические сценарии взаимодействия.

3. E2E (End-to-End) тестирование: проверка пользовательского пути

E2E-тесты имитируют действия реального пользователя: нажатие кнопок, заполнение форм, переходы между страницами. Они запускаются на полностью собранном приложении (frontend + backend + БД). Для C# веб-приложений популярен связка Selenium WebDriver или Playwright для .NET.

Пример E2E-теста с Selenium WebDriver

Проверим, что после логина пользователь видит приветствие:

public class LoginE2ETests : IDisposable
{
    private readonly IWebDriver _driver;

    public LoginE2ETests()
    {
        _driver = new ChromeDriver(); // требуется ChromeDriver.exe
        _driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
    }

    [Fact]
    public void Login_WithValidCredentials_ShowsWelcome()
    {
        // Arrange
        _driver.Navigate().GoToUrl("https://localhost:5001/login");
        _driver.FindElement(By.Id("Email")).SendKeys("user@test.com");
        _driver.FindElement(By.Id("Password")).SendKeys("Test123!");
        _driver.FindElement(By.Id("submit")).Click();

        // Assert
        var welcomeElement = _driver.FindElement(By.ClassName("welcome-message"));
        Assert.Contains("Добро пожаловать", welcomeElement.Text);
    }

    public void Dispose() => _driver.Quit();
}

Альтернатива: Используйте Playwright — он быстрее Selenium и поддерживает все современные браузеры. Установите пакет Microsoft.Playwright.NUnit.

Лучшие практики E2E

  • Не тестируйте то, что уже покрыто unit/integration тестами (например, валидацию модели).

Блоги

Рекомендаций по книгам