Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Пакет 📦 Microsoft.Extensions.TimeProvider.Testing NuGet предоставляет FakeTimeProvider класс, который обеспечивает детерминированное тестирование кода, зависящее от времени. Эта фиктивная реализация позволяет вам контролировать системное время в ваших тестах, обеспечивая предсказуемые и повторяемые результаты.
Почему используйте FakeTimeProvider
Тестирование кода, зависящего от текущего времени или использования таймеров, может быть сложной задачей:
- Недетерминированные тесты: тесты, зависящие от реального времени, могут привести к несогласованным результатам.
- Медленные тесты: тесты, которые должны ждать, пока пройдет реальное время, могут значительно замедлить выполнение тестов.
- Условия гонки: логика, зависящая от времени, может вводить условия гонки, которые трудно воспроизвести.
- Пограничные случаи: тестирование логики, зависящей от времени, в определенные моменты времени (например, полночь или границы месяца) сложно при использовании реального времени.
решает эти проблемы, принимая следующие меры:
- Предоставление полного контроля за текущим временем.
- Позволяя вам быстро продвигать время, не ожидая.
- Включение детерминированного тестирования поведения на основе времени.
- Упрощая тестирование крайних случаев и граничных условий.
Начать
Чтобы начать работу с FakeTimeProvider, установите пакет NuGet Microsoft.Extensions.TimeProvider.Testing.
dotnet add package Microsoft.Extensions.TimeProvider.Testing
Дополнительные сведения см. в разделе dotnet add package или Manage package dependencies в приложениях .NET.
Базовое использование
FakeTimeProvider расширяет TimeProvider, чтобы обеспечить управляемое время для тестирования.
var fakeTimeProvider = new FakeTimeProvider();
// Get the current time (defaults to January 1, 2000, midnight UTC).
Console.WriteLine($"Start time: {fakeTimeProvider.GetUtcNow()}");
Инициализация в заданное время
Вы можете инициализировать FakeTimeProvider с определенным временем начала:
DateTimeOffset startTime = new(2025, 10, 20, 12, 0, 0, TimeSpan.Zero);
fakeTimeProvider = new FakeTimeProvider(startTime);
Console.WriteLine($"Started at: {fakeTimeProvider.GetUtcNow()}");
Заранеее время
Метод Advance перемещает время вперед по заданной длительности:
// Advance time by 30 minutes.
fakeTimeProvider.Advance(TimeSpan.FromMinutes(30));
Console.WriteLine($"After advancing 30 minutes: {fakeTimeProvider.GetUtcNow()}");
Настройка часового пояса
Задайте локальный часовой пояс для поставщика поддельных часов:
var timeZoneId = OperatingSystem.IsWindows() ? "Pacific Standard Time" : "America/Los_Angeles";
var pacificTimeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
fakeTimeProvider.SetLocalTimeZone(pacificTimeZone);
var localTime = fakeTimeProvider.GetLocalNow();
Console.WriteLine($"Local time: {localTime}");
Тестирование отложенных операций
FakeTimeProvider особенно полезно для операций тестирования, связанных с задержками:
public class DelayedOperationTests
{
[Fact]
public async Task DelayedOperation_CompletesAfterDelay()
{
// Arrange
var fakeTimeProvider = new FakeTimeProvider();
var operation = new DelayedOperation(fakeTimeProvider);
// Act
Task task = operation.ExecuteAsync(TimeSpan.FromMinutes(5));
// Assert - operation should not be complete yet
Assert.False(task.IsCompleted);
// Advance time by 5 minutes
fakeTimeProvider.Advance(TimeSpan.FromMinutes(5));
// Wait for the task to complete
await task;
// Operation should now be complete
Assert.True(task.IsCompleted);
}
}
public class DelayedOperation(TimeProvider timeProvider)
{
public async Task ExecuteAsync(TimeSpan delay)
{
await Task.Delay(delay, timeProvider);
}
}
Тестирование периодических операций
Тестовые операции, которые выполняются периодически с помощью таймеров:
public class PeriodicOperationTests
{
[Fact]
public void PeriodicOperation_ExecutesAtIntervals()
{
// Arrange
var fakeTimeProvider = new FakeTimeProvider();
var counter = new PeriodicCounter(fakeTimeProvider);
counter.Start(TimeSpan.FromSeconds(10));
// Act & Assert
Assert.Equal(0, counter.Count);
// Advance by 10 seconds
fakeTimeProvider.Advance(TimeSpan.FromSeconds(10));
Assert.Equal(1, counter.Count);
// Advance by 20 more seconds
fakeTimeProvider.Advance(TimeSpan.FromSeconds(20));
Assert.Equal(3, counter.Count);
// Clean up
counter.Stop();
}
}
public class PeriodicCounter(TimeProvider timeProvider)
{
private ITimer? _timer;
public int Count { get; private set; }
public void Start(TimeSpan interval)
{
_timer = timeProvider.CreateTimer(
callback: _ => Count++,
state: null,
dueTime: interval,
period: interval);
}
public void Stop()
{
_timer?.Dispose();
}
}
Тестирование бизнес-логики, связанной со временем
Протестируйте бизнес-логику, зависящую от определенных времен или дат:
public class SubscriptionTests
{
[Fact]
public void Subscription_ExpiresAfterOneYear()
{
// Arrange
var fakeTimeProvider = new FakeTimeProvider();
var startDate = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
fakeTimeProvider.SetUtcNow(startDate);
var subscription = new Subscription(fakeTimeProvider);
subscription.Activate();
// Assert - subscription is active
Assert.True(subscription.IsActive);
// Act - advance time by 11 months
fakeTimeProvider.Advance(TimeSpan.FromDays(30 * 11));
Assert.True(subscription.IsActive);
// Advance time by 2 more months
fakeTimeProvider.Advance(TimeSpan.FromDays(60));
Assert.False(subscription.IsActive);
}
}
public class Subscription(TimeProvider timeProvider)
{
private DateTimeOffset _activationDate;
public void Activate()
{
_activationDate = timeProvider.GetUtcNow();
}
public bool IsActive
{
get
{
DateTimeOffset currentTime = timeProvider.GetUtcNow();
DateTimeOffset expirationDate = _activationDate.AddYears(1);
return currentTime < expirationDate;
}
}
}
Интеграция с внедрением зависимостей
Используйте FakeTimeProvider в тестах для служб, зарегистрированных с использованием внедрения зависимостей:
public class CacheServiceTests
{
[Fact]
public void Cache_ExpiresAfterTimeout()
{
// Arrange
var fakeTimeProvider = new FakeTimeProvider();
var services = new ServiceCollection();
services.AddSingleton<TimeProvider>(fakeTimeProvider);
services.AddSingleton<CacheService>();
ServiceProvider provider = services.BuildServiceProvider();
CacheService cache = provider.GetRequiredService<CacheService>();
// Act
cache.Set("key", "value", TimeSpan.FromMinutes(10));
// Assert - value is present
Assert.True(cache.TryGet("key", out string? value));
Assert.Equal("value", value);
// Advance time beyond expiration
fakeTimeProvider.Advance(TimeSpan.FromMinutes(11));
// Value should be expired
Assert.False(cache.TryGet("key", out _));
}
}
public class CacheService(TimeProvider timeProvider)
{
private readonly Dictionary<string, CacheEntry> _cache = [];
public void Set(string key, string value, TimeSpan expiration)
{
DateTimeOffset expiresAt = timeProvider.GetUtcNow() + expiration;
_cache[key] = new CacheEntry(value, expiresAt);
}
public bool TryGet(string key, out string? value)
{
if (_cache.TryGetValue(key, out CacheEntry? entry))
{
if (timeProvider.GetUtcNow() < entry.ExpiresAt)
{
value = entry.Value;
return true;
}
// Entry expired, remove it
_cache.Remove(key);
}
value = null;
return false;
}
private record CacheEntry(string Value, DateTimeOffset ExpiresAt);
}
Лучшие практики
При использовании FakeTimeProviderрассмотрите следующие рекомендации.
-
Внедрение TimeProvider: всегда внедрять
TimeProviderкак зависимость, а не использовать DateTime или DateTimeOffset напрямую. Это делает код тестируемым. - Используйте время UTC: работа с временем UTC в бизнес-логике и преобразование в локальное время только при необходимости для отображения.
-
Тестирование крайних случаев: используйте
FakeTimeProviderдля тестирования крайних случаев, таких как полночь, месячные границы, переходы на летнее время и високосные годы. -
Очистка таймеров: удаление таймеров, созданных с
CreateTimerцелью предотвращения утечки ресурсов в тестах. - Явное продвижение времени: Явно продвигайте время в ваших тестах, чтобы сделать поведение теста более ясным и предсказуемым.
- Избегайте смешивания реального и поддельного времени: не смешивайте реальные с
TimeProvider.Systemв одном и том же тесте, поскольку это может привести к непредсказуемому поведению.
См. также
- TimeProvider
- Модульное тестирование в .NET
- внедрение зависимостей в .NET