Почему тестирование C# — это не страшно, а выгодно?
Если вы только начинаете свой путь в C# и слышали, что «хороший разработчик всегда пишет тесты», но понятия не имеете, с какой стороны подойти к этому зверю — вы попали по адресу. Тестирование кода часто пугает новичков: кажется, что это что-то сложное, требующее глубоких знаний архитектуры и тонн настроек. На самом деле, написание тестов на C# — это навык, который можно освоить за один вечер, а польза от него будет длиться годами.
Представьте, что вы собрали сложный механизм из тысяч деталей. Вы же не будете включать его на полную мощность без проверки каждого узла? Так и с программой: unit-тестирование C# позволяет проверить каждый «винтик» вашего кода отдельно, не дожидаясь, пока вся система рухнет в production. В этой статье мы разберем тестирование C# с нуля: от выбора фреймворка до написания первого осмысленного теста. Поехали!
С чего начать тестирование на C#: подготовка окружения
Прежде чем писать код, давайте убедимся, что у вас есть всё необходимое. Для тестирования C# вам понадобится:
- Visual Studio 2022 (Community edition — бесплатно) или JetBrains Rider (если есть лицензия).
- .NET SDK (версия 6, 7 или 8 — подойдет любая LTS).
- Базовое понимание ООП в C#: классы, методы, интерфейсы.
Когда среда готова, создайте новый проект. Я рекомендую сразу разделять код на два проекта: основной (ваша библиотека или приложение) и тестовый. В Visual Studio это делается командой: File → New → Project → xUnit Test Project (.NET Core) или NUnit Test Project. Но мы пойдем чуть дальше и разберем оба популярных инструмента.
Выбираем фреймворк: NUnit vs xUnit
В мире C# есть два главных «игрока» для написания модульных тестов: NUnit и xUnit. Оба отлично работают, но есть нюансы:
- NUnit — классика, пришедшая из Java (аналог JUnit). Имеет атрибуты
[Test],[SetUp],[TearDown]. Очень нагляден для новичков. - xUnit — современный фреймворк от создателей ASP.NET. Использует
[Fact]и[Theory]. Считается более «чистым» и лучше интегрируется с .NET Core.
Для начинающих я советую xUnit — он проще, не требует лишних атрибутов, и его легче понять. Но если в вашей команде используют NUnit — не беда, мы рассмотрим оба варианта.
Первый юнит-тест на C#: пишем код
Допустим, у нас есть простой класс-калькулятор:
public class Calculator
{
public int Add(int a, int b) => a + b;
public int Divide(int a, int b)
{
if (b == 0)
throw new DivideByZeroException("На ноль делить нельзя!");
return a / b;
}
}
Теперь напишем тесты для этого класса. Создадим тестовый проект (xUnit) и добавим ссылку на основной проект.
Пример теста с xUnit
using Xunit;
public class CalculatorTests
{
[Fact]
public void Add_ShouldReturnSum_WhenTwoNumbers()
{
// Arrange
var calculator = new Calculator();
// Act
var result = calculator.Add(3, 5);
// Assert
Assert.Equal(8, result);
}
[Fact]
public void Divide_ShouldThrowException_WhenDivideByZero()
{
var calculator = new Calculator();
Assert.Throws<DivideByZeroException>(() => calculator.Divide(10, 0));
}
}
Обратите внимание на структуру Arrange-Act-Assert (AAA). Это золотой стандарт написания тестов: сначала готовим данные, потом выполняем действие, затем проверяем результат. Запомните этот паттерн — он спасет вас от хаоса в тестах.
Пример теста с NUnit
Для сравнения — тот же тест на NUnit:
using NUnit.Framework;
[TestFixture]
public class CalculatorTests
{
private Calculator _calculator;
[SetUp]
public void Setup()
{
_calculator = new Calculator();
}
[Test]
public void Add_ShouldReturnSum_WhenTwoNumbers()
{
var result = _calculator.Add(3, 5);
Assert.AreEqual(8, result);
}
[Test]
public void Divide_ShouldThrowException_WhenDivideByZero()
{
Assert.Throws<DivideByZeroException>(() => _calculator.Divide(10, 0));
}
}
Как видите, разница в синтаксисе минимальна, но NUnit требует больше «ритуальных» атрибутов. xUnit выглядит чище.
Тестирование с моками: Moq для начинающих
В реальных проектах ваш код почти всегда зависит от внешних сервисов: базы данных, API, файловой системы. Тестировать такие зависимости напрямую — плохая практика. Вместо этого используют mock-объекты. Самый популярный инструмент для этого в C# — Moq.
Представьте, что у нас есть сервис, который отправляет email:
public interface IEmailService
{
void Send(string to, string subject, string body);
}
public class NotificationService
{
private readonly IEmailService _emailService;
public NotificationService(IEmailService emailService)
{
_emailService = emailService;
}
public void NotifyUser(string email)
{
_emailService.Send(email, "Привет!", "Добро пожаловать в систему.");
}
}
Как протестировать NotifyUser, не отправляя реальные письма? Очень просто — используем Moq:
using Moq;
using Xunit;
public class NotificationServiceTests
{
[Fact]
public void NotifyUser_ShouldCallSend_WithCorrectParameters()
{
// Arrange
var mockEmailService = new Mock<IEmailService>();
var service = new NotificationService(mockEmailService.Object);
// Act
service.NotifyUser("test@example.com");
// Assert
mockEmailService.Verify(
e => e.Send("test@example.com", "Привет!", "Добро пожаловать в систему."),
Times.Once);
}
}
Обратите внимание: мы не создаем реальный email-сервис. Мы создаем его «заглушку» (mock) и проверяем, что метод Send был вызван с правильными аргументами. Это основа mocking C# — вы тестируете логику вашего кода, а не внешние системы.
Параметризованные тесты: меньше кода — больше покрытия
Часто нужно проверить одну и ту же логику с разными входными данными. Писать для каждого случая отдельный тест — скучно и