Какие фреймворки и подходы к unit-тестированию вы знаете в .NET?

Ответ

В экосистеме .NET unit-тестирование — стандартная практика. Выбор фреймворка и инструментов зависит от предпочтений команды и требований проекта.

Популярные фреймворки для тестирования:

  1. xUnit.net

    • Особенности: В настоящее время считается де-факто стандартом для новых проектов. Чистый дизайн, отсутствие атрибутов [SetUp]/[TearDown] (используется конструктор класса и IDisposable), поддержка параллельного запуска тестов "из коробки".
    • Пример:

      public class CalculatorTests : IDisposable
      {
          private readonly Calculator _sut; // System Under Test
      
          public CalculatorTests() // Аналог [SetUp]
          {
              _sut = new Calculator();
          }
      
          [Fact]
          public void Add_TwoPositiveNumbers_ReturnsCorrectSum()
          {
              // Arrange
              int a = 5, b = 3;
              // Act
              var result = _sut.Add(a, b);
              // Assert
              Assert.Equal(8, result);
          }
      
          [Theory]
          [InlineData(1, 2, 3)]
          [InlineData(-5, 5, 0)]
          public void Add_VariousInputs_ReturnsCorrectSum(int a, int b, int expected)
          {
              var result = _sut.Add(a, b);
              Assert.Equal(expected, result);
          }
      
          public void Dispose() // Аналог [TearDown]
          {
              _sut?.Dispose();
          }
      }
  2. NUnit

    • Особенности: Один из старейших и самых зрелых фреймворков. Богатый набор атрибутов ([SetUp], [TearDown], [TestCase]). Широко используется в legacy-проектах.
    • Пример:

      [TestFixture]
      public class StringTests
      {
          [SetUp]
          public void Setup() { ... }
      
          [Test]
          [TestCase("abc", "ABC")]
          public void ToUpper_ValidString_ReturnsUppercase(string input, string expected)
          {
              Assert.That(input.ToUpper(), Is.EqualTo(expected));
          }
      }
  3. MSTest (Microsoft.VisualStudio.TestTools.UnitTesting)

    • Особенности: Тесно интегрирован с Visual Studio. Прост в освоении, но менее гибок, чем xUnit или NUnit. Постоянно развивается.

Ключевые вспомогательные библиотеки:

  • Moq / NSubstitute: Библиотеки для создания моков (test doubles). Позволяют изолировать тестируемый код от его зависимостей.

    // Пример с Moq
    var mockLogger = new Mock<ILogger>();
    mockLogger.Setup(log => log.Write(It.IsAny<string>())).Verifiable();
    
    var service = new OrderService(mockLogger.Object);
    service.ProcessOrder(testOrder);
    
    mockLogger.Verify(); // Проверяем, что метод Write был вызван
  • FluentAssertions / Shouldly: Библиотеки для более читаемых утверждений (assertions).

    // FluentAssertions
    result.Should().Be(42);
    collection.Should().ContainSingle().Which.Name.Should().Be("John");
  • AutoFixture: Для автоматического создания тестовых данных, избавляет от ручного создания объектов в секции Arrange.

Общие лучшие практики:

  • Изоляция: Тест не должен зависеть от внешних систем (БД, файлы, API). Используйте моки.
  • Соглашение об именовании: MethodUnderTest_Scenario_ExpectedBehavior (например, ProcessOrder_EmptyCart_ThrowsException).
  • Один тест — одна логическая проверка.
  • Тесты должны быть быстрыми, повторяемыми и самодостаточными.

Ответ 18+ 🔞

А, ну это ж классика, блядь! Вся эта движуха с юнит-тестами в .NET — там реально есть где разгуляться и где наступить на грабли, ёпта. Смотри, как оно всё устроено, только без этой занудной документационной сопли.

Ну и на чём народ пишет, спросишь ты? А народ пишет на трёх основных вещах, и все друг друга готовы захуярить за выбор.

  1. xUnit.net — это сейчас типа модный мажорный фреймворк, все новые проекты на него пересаживаются. Чистенький такой, без лишнего говна. У них там, блядь, даже атрибутов [SetUp] нету, представляешь? Всё через конструктор класса делается, а если надо прибраться — IDisposable реализуешь. И тесты параллельно гоняются сразу, что иногда, конечно, приводит к весёлым пиздецам, если код не потокобезопасный.

    public class CalculatorTests : IDisposable
    {
        private readonly Calculator _sut; // System Under Test
    
        public CalculatorTests() // Аналог [SetUp]
        {
            _sut = new Calculator();
        }
    
        [Fact]
        public void Add_TwoPositiveNumbers_ReturnsCorrectSum()
        {
            // Arrange
            int a = 5, b = 3;
            // Act
            var result = _sut.Add(a, b);
            // Assert
            Assert.Equal(8, result); // Ожидаем, блядь, восьмёрку, а не хуйню какую-то!
        }
    
        public void Dispose() // Прибрал за собой — молодец
        {
            _sut?.Dispose();
        }
    }
  2. NUnit — это как старый, проверенный дед в подъезде. Сидит себе, курит, видал виды. Огромный, блядь, зоопарк всяких атрибутов: [SetUp], [TearDown], [TestCase]. В легаси-проектах его дохуя, и трогать его страшно, потому что всё работает, а как — хрен поймёшь.

  3. MSTest — это такой эталонный сын маминой подруги от Microsoft. В Visual Studio зашит, кнопочки красивые. Для начала сойдёт, но когда упрёшься в его ограничения, захочешь всё переписать на том же xUnit, ёбаный в рот.

А без этих... библиотек-помощников вообще нихуя не получится, чувак.

  • Moq / NSubstitute: Это твои палочки-выручалочки, чтобы не ебаться с реальными зависимостями. Надо проверить, что сервис логирует ошибку? Подсовываешь ему фейковый логгер, который нихуя не делает, но запоминает, что его вызывали. Красота!

    // Допустим, Moq
    var mockLogger = new Mock<ILogger>();
    // Настраиваем: "Слушай, если тебя вызовут с любой строкой — просто отметься, что работа была"
    mockLogger.Setup(log => log.Write(It.IsAny<string>())).Verifiable();
    
    var service = new OrderService(mockLogger.Object);
    service.ProcessOrder(testOrder);
    
    // А теперь проверяем: "Ну что, мудила, вызывали тебя или проёбываешь?"
    mockLogger.Verify();
  • FluentAssertions: Это когда тебе надоедает этот унылый Assert.Equal(что-то, что-то) и хочется писать утверждения, которые хоть немного похожи на человеческую речь.

    // Вместо Assert.Equal(42, result) ты пишешь:
    result.Should().Be(42);
    // Или вот: "В этой коллекции должен быть один элемент, и у него имя должно быть 'John'"
    collection.Should().ContainSingle().Which.Name.Should().Be("John");
    // Сразу видно, что тест упал, потому что там два элемента или имя не то, а не потому что "ожидалось True, но пришло False", ебись оно конём.
    
  • AutoFixture: Вообще песня, когда лень в каждом тесте вручную создавать объекты на 15 полей, из которых нужны только два. Он тебе сам насоздаёт этих сущностей, только успевай моками подменять.

И главное, блядь, запомни как "Отче наш", а то потом будешь плакать:

  • Изоляция — это святое. Тест не должен ходить в реальную базу, на реальный API или читать файлы с диска. Иначе это не юнит-тест, а какая-то ебучка интеграционная, которая падает, потому что у кого-то интернет пропал. Моки, моки и ещё раз моки!
  • Имя теста — как диагноз. ProcessOrder_EmptyCart_ThrowsException. Прочитал и сразу ясно: что тестируем, при каком условии и что должно случиться. Не Test1, блядь.
  • Один тест — одна проверка. Не надо в одном методе и заказ создавать, и почту проверять, и в БД смотреть. Сделаешь так — потом при падении будешь полчаса дебажить, чтобы понять, какая именно из десяти проверок сломалась. Пиздец.
  • Тесты должны быть быстрыми, блядь! Если твой набор тестов выполняется пять минут, ты будешь их запускать раз в неделю, а толку от них тогда? Ноль ебать. Быстрые, повторяемые и независимые.

Вот так вот, если коротко и без прикрас. Выбирай xUnit, не ошибёшься, обкладывай Moq'ом и FluentAssertions, и не выёбывайся с интеграцией там, где не надо. Всё, можно идти писать код, который не развалится от первого же коммита коллеги-распиздяя.