Писал ли тесты на C#?

«Писал ли тесты на C#?» — вопрос из категории Тестирование, который задают на 28% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, активно пишу модульные, интеграционные и end-to-end тесты в экосистеме .NET. В работе использую следующий стек и подходы:

Стек технологий:

  • Фреймворки: xUnit (предпочтительно), NUnit, MSTest
  • Mocking: Moq, NSubstitute
  • Assertions: FluentAssertions для читаемых проверок
  • Интеграционное тестирование: Testcontainers для поднятия реальных БД в Docker, WebApplicationFactory для ASP.NET Core

Пример модульного теста с xUnit и Moq:

public class OrderServiceTests
{
    private readonly Mock<IRepository<Order>> _mockRepo;
    private readonly OrderService _sut; // System Under Test

    public OrderServiceTests()
    {
        _mockRepo = new Mock<IRepository<Order>>();
        _sut = new OrderService(_mockRepo.Object);
    }

    [Fact]
    public async Task ProcessOrder_ValidOrder_CallsRepositoryAndReturnsId()
    {
        // Arrange
        var order = new Order { Id = 0, Total = 100 };
        _mockRepo.Setup(r => r.AddAsync(It.IsAny<Order>()))
                 .Callback<Order>(o => o.Id = 123) // Симулируем присвоение ID
                 .Returns(Task.CompletedTask);

        // Act
        var resultId = await _sut.ProcessOrderAsync(order);

        // Assert
        resultId.Should().Be(123); // FluentAssertions
        _mockRepo.Verify(r => r.AddAsync(order), Times.Once);
        _mockRepo.VerifyNoOtherCalls();
    }

    [Theory]
    [InlineData(0)]
    [InlineData(-10)]
    public void ValidateOrder_InvalidTotal_ThrowsException(decimal invalidTotal)
    {
        var order = new Order { Total = invalidTotal };
        Assert.Throws<ArgumentException>(() => _sut.ValidateOrder(order));
    }
}

Ключевые практики:

  1. Изоляция: Тесты не зависят от внешних сервисов (используются моки) и друг от друга.
  2. Паттерн AAA: Чёткое разделение на Arrange (подготовка), Act (действие), Assert (проверка).
  3. Смысловые имена: Имена тестовых методов следуют шаблону MethodName_StateUnderTest_ExpectedBehavior.
  4. CI/CD интеграция: Тесты запускаются автоматически в пайплайнах (GitHub Actions, Azure DevOps).
  5. Покрытие критических путей: Фокус на тестировании бизнес-логики, edge-кейсов и обработки ошибок, а не на достижении формального процента покрытия.