Тестирование в C# (.NET): обзор фреймворков и инструментов
Качественное тестирование — основа надёжного ПО на платформе .NET. В экосистеме C# существует несколько зрелых фреймворков для модульного (unit), интеграционного и приёмочного тестирования. В этой статье мы разберём основные инструменты, их особенности и приведём практические примеры кода.
Основные фреймворки для unit-тестирования
В .NET Core и .NET 5+ встроенная поддержка тестирования реализована через три основных фреймворка:
- MSTest — классический фреймворк от Microsoft, простой для начинающих.
- NUnit — популярный open-source фреймворк с богатым набором атрибутов.
- xUnit.net — современный, лёгкий и гибкий инструмент, созданный автором NUnit.
Все три поддерживаются в Visual Studio, Rider и через dotnet CLI. Выбор зависит от предпочтений команды, но xUnit и NUnit считаются более гибкими для сложных сценариев.
Пример теста на xUnit
Рассмотрим тестирование простого калькулятора с использованием xUnit:
using Xunit;
public class Calculator
{
public int Add(int a, int b) => a + b;
public int Divide(int a, int b) => b == 0 ? throw new DivideByZeroException() : a / b;
}
public class CalculatorTests
{
[Fact]
public void Add_ShouldReturnSum_WhenTwoNumbers()
{
// Arrange
var calc = new Calculator();
// Act
var result = calc.Add(3, 5);
// Assert
Assert.Equal(8, result);
}
[Theory]
[InlineData(10, 2, 5)]
[InlineData(9, 3, 3)]
[InlineData(7, 1, 7)]
public void Divide_ShouldReturnQuotient(int a, int b, int expected)
{
var calc = new Calculator();
var result = calc.Divide(a, b);
Assert.Equal(expected, result);
}
[Fact]
public void Divide_ShouldThrow_WhenDivideByZero()
{
var calc = new Calculator();
Assert.Throws<DivideByZeroException>(() => calc.Divide(5, 0));
}
}
Атрибут [Fact] обозначает обычный тест, а [Theory] с [InlineData] позволяет запускать один тест с разными входными данными.
Инструменты для mock-объектов
При unit-тестировании часто нужно изолировать тестируемый класс от внешних зависимостей (базы данных, API, файловая система). Для этого применяются mock-фреймворки:
- Moq — самый популярный, простой синтаксис, хорошая документация.
- NSubstitute — более лаконичный, с fluent-интерфейсом.
- FakeItEasy — дружелюбный API для быстрого создания заглушек.
Пример использования Moq
Допустим, у нас есть сервис, который отправляет email. Мы хотим протестировать логику без реальной отправки:
using Moq;
using Xunit;
public interface IEmailSender
{
void Send(string to, string subject, string body);
}
public class NotificationService
{
private readonly IEmailSender _sender;
public NotificationService(IEmailSender sender) => _sender = sender;
public void NotifyUser(string email, string message)
{
if (string.IsNullOrWhiteSpace(email))
throw new ArgumentException("Email is required");
_sender.Send(email, "Notification", message);
}
}
public class NotificationServiceTests
{
[Fact]
public void NotifyUser_ShouldCallSend_WhenValidEmail()
{
// Arrange
var mockSender = new Mock<IEmailSender>();
var service = new NotificationService(mockSender.Object);
// Act
service.NotifyUser("test@example.com", "Hello!");
// Assert
mockSender.Verify(s => s.Send("test@example.com", "Notification", "Hello!"), Times.Once);
}
[Fact]
public void NotifyUser_ShouldThrow_WhenEmailEmpty()
{
var mockSender = new Mock<IEmailSender>();
var service = new NotificationService(mockSender.Object);
Assert.Throws<ArgumentException>(() => service.NotifyUser("", "Message"));
}
}
Moq автоматически создаёт заглушку интерфейса, а метод Verify проверяет, что вызов произошёл с нужными параметрами.
Fluent Assertions — читаемые проверки
Библиотека FluentAssertions делает код проверок более естественным и читаемым. Вместо Assert.Equal(expected, actual) вы пишете actual.Should().Be(expected):
using FluentAssertions;
using Xunit;
public class Также в библиотеке
Мониторинг C++ приложений: библиотека metrics для сбора метрик
Работа с датами в Kotlin: обзор библиотек и инструментов
Работа с изображениями в Go (Golang): image, draw, imaging, resize