Тестируемый код: DI, TimeProvider, границы

↑ 2.4 Тестирование в .NET — основы · ← Предыдущая · → Следующая

Обязательно~3 мин чтенияУровень: middle

Зачем это на собесе

Проверка проектного мышления: почему код «не тестируется» и как это исправить не моками, а дизайном.

Объяснение

Код плохо тестируется, когда логика смешана с побочными эффектами и скрытыми зависимостями: DateTime.Now, new HttpClient(), статика, синглтоны, Random.

ПроблемаРешение
Текущее времяTimeProvider (.NET 8) + FakeTimeProvider
Случайность и Guidвнедрять источник или seed
Файловая система, сетьабстракция на границе или реальная зависимость в интеграционном тесте
Статические вызовыобёртка или перенос в композиционный корень
Бизнес-логика вместе с I/O«функциональное ядро, императивная оболочка»
public class TokenService(TimeProvider clock)
{
    public bool IsExpired(Token t) => t.ExpiresAt <= clock.GetUtcNow();
}
 
// в тесте
var time = new FakeTimeProvider(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero));
var sut = new TokenService(time);
time.Advance(TimeSpan.FromHours(2));
Assert.True(sut.IsExpired(token));

Функциональное ядро

Чистая логика принимает данные и возвращает решение, оболочка читает и пишет данные. Ядро тестируется без моков.

// чистая функция
static Decision Evaluate(Order o, DateTimeOffset now) => /* ... */;
 
// оболочка
public async Task HandleAsync(Guid id, CancellationToken ct)
{
    var order = await repo.GetAsync(id, ct);
    var decision = Evaluate(order, clock.GetUtcNow());
    await repo.SaveAsync(decision, ct);
}

Нюансы и подводные камни

  • Конструктор с 10 зависимостями — признак нарушения SRP, а не проблемы тестов.
  • Не выносите в интерфейс всё подряд: интерфейс ради мока без второй реализации лишний.
  • Service locator и статический доступ к контейнеру скрывают зависимости.
  • internal члены можно открыть тестам через InternalsVisibleTo, но лучше тестировать публичное поведение.

Практика

  1. Замените DateTime.UtcNow в проекте на TimeProvider.
  2. Выделите чистую функцию из метода с I/O и покройте её табличными тестами.
  3. Найдите класс с максимальным числом зависимостей и разбейте.

Вопросы с ответами

Связанные темы