← Назад к вопросам

Какие знаешь правила написания тестов?

2.0 Middle🔥 162 комментариев
#Тестирование

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Основные правила написания тестов для C# Backend

Написание эффективных и поддерживаемых тестов — критически важный навык для backend-разработчика. Вот ключевые правила и принципы, которых я придерживаюсь:

1. Принципы FIRST

Тесты должны соответствовать акрониму FIRST:

  • Fast (Быстрые): Тесты должны выполняться быстро, чтобы их можно было запускать часто. Медленные тесты (особенно интеграционные) обычно выносятся в отдельную фазу CI/CD.

  • Independent (Независимые): Каждый тест должен быть изолирован и не зависеть от состояния, созданного другими тестами. Это гарантирует стабильность и предсказуемость.

  • Repeatable (Повторяемые): Тест должен давать одинаковый результат при каждом запуске в любой среде (локально, на CI-сервере).

  • Self-Validating (Самопроверяющиеся): Тест должен однозначно определять — пройден он или упал, без необходимости ручной проверки.

  • Timely (Своевременные): Тесты пишутся одновременно с кодом (TDD) или сразу после него, а не в конце разработки.

2. Структура AAA (Arrange-Act-Assert)

Каждый тест должен четко разделяться на три фазы:

[Fact]
public void CalculateTotal_WithMultipleItems_ReturnsCorrectSum()
{
    // Arrange: Подготовка данных и зависимостей
    var cart = new ShoppingCart();
    cart.AddItem(new CartItem { Price = 100, Quantity = 2 });
    cart.AddItem(new CartItem { Price = 50, Quantity = 1 });
    var calculator = new PriceCalculator();
    
    // Act: Выполнение тестируемого действия
    decimal total = calculator.CalculateTotal(cart);
    
    // Assert: Проверка результата
    Assert.Equal(250, total);
}

3. Правила именования тестов

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

  • MethodName_StateUnderTest_ExpectedBehavior
  • Should_ExpectedBehavior_When_StateUnderTest
// Пример хорошего именования
[Fact]
public void ProcessOrder_WithInvalidStatus_ThrowsOrderException()
{
    // ...
}

[Fact]
public void Should_ReturnNull_When_UserNotFound()
{
    // ...
}

4. Изоляция и Mocking

  • Используйте моки для внешних зависимостей (базы данных, API, файловой системы)
  • Предпочитайте конструктор внедрения зависимостей для легкого тестирования
  • Избегайте статических классов и синглтонов в бизнес-логике
public class OrderServiceTests
{
    [Fact]
    public void PlaceOrder_ValidRequest_CallsRepositoryAndPublisher()
    {
        // Arrange
        var mockRepo = new Mock<IOrderRepository>();
        var mockPublisher = new Mock<IMessagePublisher>();
        var service = new OrderService(mockRepo.Object, mockPublisher.Object);
        
        // Act
        service.PlaceOrder(new OrderRequest());
        
        // Assert
        mockRepo.Verify(r => r.Save(It.IsAny<Order>()), Times.Once);
        mockPublisher.Verify(p => p.Publish(It.IsAny<OrderPlacedEvent>()), Times.Once);
    }
}

5. Покрытие и качество

  • Тестируйте поведение, а не реализацию: Тесты должны проверять, что код делает то, что нужно, а не как он это делает.
  • Избегайте тестирования тривиального кода (геттеры/сеттеры, простые маппинги).
  • Стремитесь к высокому покрытию критических путей, но не гонитесь за 100% покрытием любой ценой.
  • Используйте метрики осмысленно: 70% хорошо написанных тестов лучше, чем 95% хрупких.

6. Организация тестов

  • Разделяйте юнит-тесты, интеграционные и e2e тесты
  • Используйте разные проекты или папки для разных типов тестов
  • Группируйте тесты по функциональности, а не по техническому признаку
Tests/
├── Unit/
│   ├── Services/
│   ├── Domain/
│   └── Application/
├── Integration/
│   ├── DatabaseTests/
│   └── ApiTests/
└── EndToEnd/

7. Работа с данными

  • Используйте тестовые данные непосредственно в тестах (встроенные или из фабрик)
  • Для интеграционных тестов применяйте подходы:
    • Testcontainers для изолированных БД
    • In-memory базы данных (с осторожностью)
    • Транзакции с откатом
public class UserRepositoryTests : IClassFixture<DatabaseFixture>
{
    [Fact]
    public async Task GetUser_ExistingId_ReturnsUser()
    {
        using var context = new TestDbContext();
        
        // Arrange: Явное добавление тестовых данных
        var testUser = new User { Id = 1, Name = "Test" };
        context.Users.Add(testUser);
        await context.SaveChangesAsync();
        
        // Act & Assert
        var user = await context.Users.FindAsync(1);
        Assert.NotNull(user);
        Assert.Equal("Test", user.Name);
    }
}

8. Антипаттерны и что избегать

Хрупкие тесты, зависящие от времени, случайных данных или порядка выполнения
Тесты без ассертов или с пропущенными исключениями
Избыточные проверки одного и того же в разных тестах
Тестирование приватных методов через рефлексию
Игнорирование падающих тестов ("пока закомментирую")

9. Современные практики для C#

  • Используйте FluentAssertions для читаемых проверок:
result.Should().NotBeNull()
      .And.Subject.As<User>().Name.Should().Be("John");
  • Применяйте параметризованные тесты для проверки граничных значений:
[Theory]
[InlineData(0, 0)]
[InlineData(1, 1)]
[InlineData(5, 120)]
[InlineData(10, 3628800)]
public void Factorial_ValidInput_ReturnsCorrectResult(int n, long expected)
{
    var result = MathHelper.Factorial(n);
    Assert.Equal(expected, result);
}
  • Настраивайте тестовое окружение через конструкторы и фикстуры в xUnit

10. Интеграция в процесс разработки

  • Тесты — часть Definition of Done
  • Все тесты должны проходить перед созданием PR
  • Регрессионные тесты добавляются для каждого исправленного бага
  • Рефакторинг тестов так же важен, как рефакторинг основного кода

Качество тестового кода напрямую влияет на скорость разработки и стабильность системы. Хорошо написанные тесты служат живой документацией, позволяют безопасно рефакторить и дают уверенность при развертывании изменений. В контексте C# backend особенно важно уделять внимание тестированию асинхронных операций, работы с БД, интеграции с внешними сервисами и обработке исключений.