FakeTimeProvider ile test etme

📦 Microsoft.Extensions.TimeProvider.Testing NuGet paketi, zamana bağlı olan kodun belirlenimci testini sağlayan bir FakeTimeProvider sınıf sağlar. Bu sahte uygulama, testlerinizin içindeki sistem süresini kontrol etmenize ve tahmin edilebilir ve tekrarlanabilir sonuçlar elde etmenizi sağlar.

FakeTimeProvider neden kullanılır?

Geçerli zamana bağlı olan veya zamanlayıcıları kullanan kodu test etme zor olabilir:

  • Belirlenemeyen testler: Gerçek zamanlıya bağlı testler tutarsız sonuçlar üretebilir.
  • Yavaş testler: Gerçek sürenin geçmesini beklemesi gereken testler, test yürütmeyi önemli ölçüde yavaşlatabilir.
  • Yarış koşulları: Zamana bağlı mantık, yeniden oluşturması zor olan yarış koşullarına neden olabilir.
  • Uç durumlar: Belirli zamanlarda (gece yarısı veya ay sınırları gibi) zamana dayalı mantığı test etme gerçek zamanlı olarak zordur.

Bu FakeTimeProvider zorlukları şu şekilde ele alır:

  • Geçerli saat üzerinde tam denetim sağlama.
  • Beklemeden zamanı anında ilerletmenizi sağlar.
  • Zaman tabanlı davranışın belirlenimci testini etkinleştirme.
  • Uç durumları ve sınır koşullarını test etmeyi kolaylaştırır.

Başlayın

FakeTimeProvider ile başlamaya başlamak için Microsoft.Extensions.TimeProvider.Testing NuGet paketini yükleyin.

dotnet add package Microsoft.Extensions.TimeProvider.Testing

Daha fazla bilgi için bkz. dotnet add package veya .NET uygulamalarında paket bağımlılıklarını yönetme.

Temel kullanım

FakeTimeProvider test için kontrol edilebilir süre sağlamak amacıyla TimeProvider'i genişletir.

var fakeTimeProvider = new FakeTimeProvider();

// Get the current time (defaults to January 1, 2000, midnight UTC).
Console.WriteLine($"Start time: {fakeTimeProvider.GetUtcNow()}");

Belirli bir saatle başlatma

Belirli bir başlangıç saatiyle başlatabilirsiniz FakeTimeProvider :

DateTimeOffset startTime = new(2025, 10, 20, 12, 0, 0, TimeSpan.Zero);
fakeTimeProvider = new FakeTimeProvider(startTime);

Console.WriteLine($"Started at: {fakeTimeProvider.GetUtcNow()}");

Zamanı İlerlet

Belirtilen süre boyunca Advance yöntemini kullanarak zamanı ileri taşır:

// Advance time by 30 minutes.
fakeTimeProvider.Advance(TimeSpan.FromMinutes(30));
Console.WriteLine($"After advancing 30 minutes: {fakeTimeProvider.GetUtcNow()}");

Saat dilimini yapılandırma

Sahte saat sağlayıcısı için yerel saat dilimini ayarlayın:

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}");

Gecikmeli işlemleri test edin

FakeTimeProvider özellikle gecikmeler içeren test işlemleri için kullanışlıdır:

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);
    }
}

Düzenli işlemleri test edin

Zamanlayıcıları kullanarak düzenli aralıklarla yürütülen test işlemleri:

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();
    }
}

Zamana dayalı iş mantığını test edin

Belirli saatlere veya tarihlere bağlı iş mantığını test edin:

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;
        }
    }
}

Bağımlılık enjeksiyonu ile tümleştirme

Bağımlılık ekleme ile kaydedilen hizmetler için testlerde kullanın 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);
}

En iyi yöntemler

kullanırken FakeTimeProvideraşağıdaki en iyi yöntemleri göz önünde bulundurun:

  • TimeProvider Enjekte Et: TimeProvider veya DateTime doğrudan kullanmak yerine her zaman DateTimeOffset'yı bağımlılık olarak enjekte edin. Bu, kodunuzu test edilebilir hale getirir.
  • UTC saatini kullanma: İş mantığınızda UTC saatiyle çalışın ve yalnızca görüntüleme için gerektiğinde yerel saate dönüştürün.
  • Uç durumları test etme: Gece yarısı, ay sınırları, yaz saati geçişleri ve artık yıllar gibi uç durumları test etmek için FakeTimeProvider kullanın.
  • Zamanlayıcıları temizleme: Testlerinizde kaynak sızıntılarını önlemek için ile CreateTimer oluşturulan zamanlayıcıları atın.
  • Süreyi kasıtlı olarak ilerlet: Test davranışını net ve öngörülebilir hale getirmek için testlerinizde süreyi açıkça ilerletin.
  • Gerçek ve sahte zamanı karıştırmaktan kaçının: Aynı testte gerçek TimeProvider.System ile FakeTimeProvider karıştırmayın, çünkü bu öngörülemeyen davranışlara yol açabilir.

Ayrıca bakınız