Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Методы и библиотеки для Assert в C#
Я использовал встроенные механизмы фреймворков для модульного тестирования, а также специализированные библиотеки для различных сценариев валидации. Вот основные инструменты, которые применял в своей практике:
1. Встроенные Assert-методы NUnit/xUnit
NUnit предоставляет классический набор методов через класс Assert:
// Базовые проверки
Assert.AreEqual(expected, actual);
Assert.IsTrue(condition);
Assert.IsNull(obj);
Assert.IsInstanceOf<MyType>(obj);
// Коллекции
CollectionAssert.AreEqual(expectedList, actualList);
CollectionAssert.Contains(collection, item);
// Исключения
Assert.Throws<InvalidOperationException>(() => method());
xUnit использует более лаконичный подход:
// В xUnit нет статического класса Assert
Assert.Equal(expected, actual);
Assert.True(condition);
Assert.Null(obj);
Assert.IsType<MyType>(obj);
// Проверка исключений
var exception = Assert.Throws<InvalidOperationException>(() => method());
Assert.Equal("Expected message", exception.Message);
2. Fluent Assertions - основная библиотека
Для читаемых и выразительных проверок активно использую FluentAssertions:
// Более читаемый синтаксис
actual.Should().Be(expected);
condition.Should().BeTrue();
obj.Should().BeNull();
obj.Should().BeOfType<MyType>();
// Цепочки проверок
result.Should()
.NotBeNull()
.And.HaveCount(5)
.And.Contain(item => item.Id > 0);
// Строки
text.Should()
.StartWith("Hello")
.And.EndWith("World")
.And.Contain("test");
// Исключения
action.Should()
.Throw<InvalidOperationException>()
.WithMessage("Expected message*")
.Where(ex => ex.ErrorCode == 100);
// Коллекции
collection.Should()
.HaveCount(3)
.And.ContainSingle(x => x.IsActive)
.And.OnlyHaveUniqueItems();
3. Специализированные проверки
Для разных типов данных применяю соответствующие методы:
// Даты и время
dateTime.Should().BeCloseTo(expected, TimeSpan.FromSeconds(1));
// Числа с точностью
number.Should().BeApproximately(3.14, 0.01);
// События
monitor.Should().Raise("EventName")
.WithArgs<EventArgs>(args => args.IsValid);
// JSON
jsonString.Should().BeEquivalentTo(expectedJson);
4. Пользовательские Assert-методы
Создаю кастомные методы проверки для доменных объектов:
public static class CustomAssertions
{
public static void ShouldBeValidOrder(this Order order)
{
order.Should().NotBeNull();
order.Id.Should().BePositive();
order.Items.Should().NotBeEmpty();
order.TotalAmount.Should().BeGreaterThan(0);
// Бизнес-правила
if (order.Type == OrderType.Express)
{
order.DeliveryDate.Should().BeWithin(3).Days();
}
}
public static void ShouldContainError(this ValidationResult result, string code)
{
result.Errors.Should()
.Contain(e => e.Code == code,
$"ожидалась ошибка с кодом '{code}'");
}
}
5. Assert для асинхронного кода
Для тестирования асинхронных операций:
// Асинхронные проверки
await asyncMethod.Should().NotThrowAsync();
var result = await asyncMethod.Should().CompleteWithinAsync(TimeSpan.FromSeconds(5));
// Асинхронные исключения
await asyncAction.Should()
.ThrowAsync<HttpException>()
.Where(ex => ex.StatusCode == 404);
6. Дополнительные библиотеки
В зависимости от контекста использую:
- AutoFixture.AutoNSubstitute для создания mock-объектов с автоматической настройкой
- Moq или NSubstitute для проверки взаимодействий (Assert на вызовы методов)
- Bogus для генерации тестовых данных с последующей проверкой
- Snapshot testing (Snapshooter) для сравнения сложных объектов
7. Best Practices
Из своего опыта выделяю следующие принципы:
- Читаемость превыше всего - тесты должны быть понятны даже не-разработчикам
- Конкретные сообщения об ошибках - всегда добавляю поясняющие сообщения
- Одна проверка на тест - каждый тест проверяет одну конкретную вещь
- Использование правильных матчеров - например,
BeEquivalentToвместо поэлементного сравнения для объектов - Проверка не только happy path - обязательно тестирую граничные случаи и ошибки
Пример комплексного использования:
[Test]
public async Task ProcessOrder_ValidRequest_ReturnsSuccess()
{
// Arrange
var order = new OrderBuilder().WithItems(3).Build();
var service = new OrderService();
// Act
var result = await service.ProcessOrderAsync(order);
// Assert
result.Should().NotBeNull();
result.Status.Should().Be(OrderStatus.Processed);
result.ProcessedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
result.Total.Should().Be(order.Items.Sum(i => i.Price * i.Quantity));
// Проверка side effects
_loggerMock.Verify(x => x.LogInfo("Order processed"), Times.Once);
_repositoryMock.Verify(x => x.SaveAsync(It.IsAny<Order>()), Times.Once);
}
Ключевой вывод: выбор инструментов Assert зависит от контекста - для простых unit-тестов достаточно встроенных методов, для сложных интеграционных тестов и требующих высокой читаемости лучше использовать Fluent Assertions в комбинации со специализированными библиотеками.