От .NET-разработчика ждут уверенного xUnit, понимания моков и умения писать интеграционные тесты на ASP.NET Core с настоящей базой.
xUnit
public class CalculatorTests
{
[Fact]
public void Divide_by_zero_throws() =>
Assert.Throws<DivideByZeroException>(() => Calculator.Divide(1, 0));
[Theory]
[InlineData(2, 3, 5)]
[InlineData(-1, 1, 0)]
public void Adds(int a, int b, int expected) =>
Assert.Equal(expected, Calculator.Add(a, b));
}
- Новый экземпляр класса на каждый тест: конструктор вместо setup,
IDisposable/IAsyncLifetimeвместо teardown. - Общая дорогая инициализация:
IClassFixture<T>,ICollectionFixture<T>. - Асинхронный тест возвращает
Task, никогда неasync void.
NUnit и MSTest тоже встречаются, принципы те же.
Структура теста
Arrange, Act, Assert. Один тест проверяет одно поведение, имя описывает сценарий: Withdraw_more_than_balance_returns_error. Для читаемых проверок многие используют FluentAssertions или Shouldly.
Моки
Стаб отдаёт заготовленные ответы, мок ещё и проверяет взаимодействие.
var mail = new Mock<IMailSender>();
var sut = new RegistrationService(repo, mail.Object);
await sut.RegisterAsync("ira@example.com");
mail.Verify(m => m.SendAsync("ira@example.com", It.IsAny<string>()), Times.Once);
Мокайте границы системы (почта, платежи, внешние API), а не всё подряд. Тест, который проверяет каждый внутренний вызов, ломается от любого рефакторинга. Не мокайте DbContext и IQueryable: мок не воспроизводит перевод в SQL.
Время и случайность
Код с DateTime.UtcNow нетестируем. В .NET 8 внедряйте TimeProvider, в тестах FakeTimeProvider с Advance(...).
Интеграционные тесты ASP.NET Core
public class OrdersApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public OrdersApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.WithWebHostBuilder(b => b.ConfigureTestServices(s =>
s.AddSingleton<IPaymentGateway, FakePaymentGateway>())).CreateClient();
}
[Fact]
public async Task Get_unknown_order_returns_404()
{
var resp = await _client.GetAsync("/orders/999999");
Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
}
}
WebApplicationFactory поднимает настоящий конвейер в памяти: DI, middleware, сериализация, валидация. Чтобы Program был виден из тестов, добавляют public partial class Program;.
База в тестах
InMemory-провайдер EF Core не проверяет ограничения и транзакции, поэтому для логики работы с данными берут настоящую СУБД: Testcontainers поднимает PostgreSQL в Docker из кода теста. Между тестами данные чистят (Respawn) или откатывают транзакцию. SQLite in-memory это компромисс, если Docker недоступен.
Пирамида
Много быстрых модульных тестов, меньше интеграционных, немного сквозных. Для бэкенда часто выгоден «трофей»: упор на интеграционные тесты API, которые дают больше уверенности за те же усилия.
Частые ошибки
async voidв тестах.- Зависимость тестов друг от друга через общие данные.
- Проверка реализации вместо поведения.
- InMemory вместо настоящей базы.
Thread.Sleepв тестах вместо управляемого времени.
Что спросят на собесе
- Чем
[Fact]отличается от[Theory]? - Чем мок отличается от стаба, когда моки вредят?
- Как написать интеграционный тест для ASP.NET Core?
- Почему не стоит тестировать на InMemory-провайдере?
- Как тестировать код, зависящий от текущего времени?