Какие атрибуты для написания тестов в C# вы используете чаще всего?

Ответ

Я преимущественно работаю с xUnit как с наиболее гибким и современным фреймворком для .NET. Вот ключевые атрибуты и их применение:

Основные атрибуты xUnit:

  • [Fact] – помечает метод как тестовый, который должен выполняться без внешних данных.
    [Fact]
    public void CalculateTotal_EmptyCart_ReturnsZero()
    {
        var cart = new ShoppingCart();
        var total = cart.CalculateTotal();
        Assert.Equal(0, total);
    }
  • [Theory] – помечает параметризованный тест. Данные для него предоставляются отдельными атрибутами.
  • [InlineData] – самый простой способ передать данные в [Theory]. Данные задаются прямо в атрибуте.
    [Theory]
    [InlineData(5, 3, 8)]  // a, b, expected
    [InlineData(-1, 1, 0)]
    [InlineData(0, 0, 0)]
    public void Add_TwoNumbers_ReturnsSum(int a, int b, int expected)
    {
        var calculator = new Calculator();
        var result = calculator.Add(a, b);
        Assert.Equal(expected, result);
    }
  • [MemberData] – позволяет использовать статическое свойство или метод класса для предоставления сложных наборов тестовых данных (например, объекты).

    public static IEnumerable<object[]> TestData =>
        new List<object[]>
        {
            new object[] { new User("Alice", 30), true },
            new object[] { new User("Bob", 17), false }
        };
    
    [Theory]
    [MemberData(nameof(TestData))]
    public void User_IsAdult_ReturnsCorrectValue(User user, bool expectedIsAdult)
    {
        var result = user.IsAdult();
        Assert.Equal(expectedIsAdult, result);
    }
  • [ClassData] – похож на [MemberData], но данные предоставляет отдельный класс, реализующий IEnumerable<object[]>. Удобно для организации больших наборов данных.

Атрибуты для управления выполнением:

  • [Trait("Category", "Integration")] – для категоризации тестов (например, чтобы запускать только "Unit" или только "Integration" тесты в пайплайне).
  • [Fact(Skip = "Требуется доработка моков")] – временно отключает тест.

Сравнение с другими фреймворками:

  • NUnit: [Test] (аналог [Fact]), [TestCase] (аналог [InlineData]).
  • MSTest: [TestMethod], [DataTestMethod], [DataRow].

Практический совет: Используйте [Theory] с [MemberData] или [ClassData] для тестирования пограничных случаев (boundary values) и различных сценариев, чтобы избежать дублирования кода тестов.

Ответ 18+ 🔞

Слушай, а вот xUnit — это вообще огонь, если ты на .NET работаешь. Гибкий, современный, без лишнего олдскульного говна. Я на нём собаку съел, так что давай разжёвываю, как есть.

Основные штуки, без которых нихуя не работает:

  • [Fact] — это типа базовый тест, который сам по себе, без всяких подачек данных. Написал логику, проверил — всё.

    [Fact]
    public void CalculateTotal_EmptyCart_ReturnsZero()
    {
        var cart = new ShoppingCart();
        var total = cart.CalculateTotal();
        Assert.Equal(0, total);
    }

    Вот, видишь? Корзина пустая — итог ноль. Никаких подвохов.

  • [Theory] — а вот это уже интереснее. Это когда один тест, но ты его прогоняешь на разных данных. Как будто один сценарий, но с разными вводными. Без него — копипаста до скончания времён.

  • [InlineData] — самый простой способ накормить [Theory]. Данные прямо в атрибут пихаешь, и поехали.

    [Theory]
    [InlineData(5, 3, 8)]  // a, b, expected
    [InlineData(-1, 1, 0)]
    [InlineData(0, 0, 0)]
    public void Add_TwoNumbers_ReturnsSum(int a, int b, int expected)
    {
        var calculator = new Calculator();
        var result = calculator.Add(a, b);
        Assert.Equal(expected, result);
    }

    Смотри, один метод, а проверяет три случая сразу. Красота же, ёпта!

  • [MemberData] — а это когда [InlineData] уже не лезет, потому что данные сложные. Тут ты тащишь их из отдельного свойства или метода в том же классе. Идеально для объектов.

    public static IEnumerable<object[]> TestData =>
        new List<object[]>
        {
            new object[] { new User("Alice", 30), true },
            new object[] { new User("Bob", 17), false }
        };
    
    [Theory]
    [MemberData(nameof(TestData))]
    public void User_IsAdult_ReturnsCorrectValue(User user, bool expectedIsAdult)
    {
        var result = user.IsAdult();
        Assert.Equal(expectedIsAdult, result);
    }

    Видал? Целые объекты User в тест засунул. Мощь!

  • [ClassData] — ну а это уже для совсем распиздяйских больших наборов данных. Выносишь всё в отдельный класс, чтобы в основном тесте не засирать глаза.

Атрибуты, чтобы управлять этим цирком:

  • [Trait("Category", "Integration")] — вешаешь бирку на тест. Типа «интеграционный», «медленный», «пиздец какой важный». Потом в пайплайне можешь только быстрые unit-тесты гонять, а эти — отдельно.
  • [Fact(Skip = "Требуется доработка моков")] — классика жанра. Тест сломался, а фиксить сейчас влом? Щёлк — и он пропускается. Главное потом не забыть, а то так и будут висеть, эти «временно отключённые», пока все не похудеют.

Если вдруг вспомнил про старьё:

  • NUnit: там [Test] — это как наш [Fact], а [TestCase] — аналог [InlineData]. В целом, тоже ничего, но xUnit как-то поприятнее.
  • MSTest: [TestMethod], [DataTestMethod], [DataRow]. Ну, сойдёт, если ты любитель микроскопом гвозди забивать.

Совет от бывалого, чтобы не выстрелить себе в ногу: Не городи кучу одинаковых [Fact] с разными цифрами. Бери [Theory] и корми его через [MemberData] или [ClassData] всеми пограничными случаями — нулями, отрицательными числами, null'ами, херовыми строками. Так и покрытие лучше, и сам не сойдёшь с ума от однообразия.