عنوان:

‫استفاده از TimeProvider دات‌نت 8 برای تست کدنویسی وابسته به زمان


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۱/۱۱ ۰۹:۲۱
آدرس: www.dntips.ir
مقدمه
در دنیای برنامه‌نویسی، مدیریت زمان و تاریخ همیشه یکی از چالش‌های اصلی بوده است، به‌ویژه زمانی که نیاز به تست واحد (Unit Testing) کدهایی داریم که به زمان فعلی وابسته هستند. در نسخه‌های قدیمی‌تر فریم‌ورک .NET، توسعه‌دهندگان برای تست چنین کدهایی مجبور به استفاده از راه‌حل‌های پیچیده مانند رابط‌ها (Interfaces) یا ابزارهای شخص ثالث بودند. با معرفی .NET 8، مایکروسافت یک ابزار قدرتمند به نام TimeProvider را ارائه کرده است که این مشکلات را به شکلی ساده و کارآمد حل می‌کند. این ابزار به توسعه‌دهندگان اجازه می‌دهد تا زمان را در تست‌ها کنترل کنند و از وابستگی مستقیم به زمان سیستم‌عامل (مانند DateTime.Now) رها شوند. در این مقاله، به بررسی مفهوم TimeProvider، قابلیت‌های آن و نحوه استفاده از آن در تست واحد پرداخته می‌شود. هدف این است که توسعه‌دهندگان .NET بتوانند با استفاده از این ابزار، تست‌هایی پایدارتر و قابل اعتمادتر بنویسند.

چالش‌های تست کدهای وابسته به زمان
پیش از .NET 8، تست کدهایی که به زمان وابسته بودند، با مشکلاتی همراه بود. به عنوان مثال، استفاده از DateTime.Now یا DateTimeOffset.UtcNow به دلیل ماهیت استاتیک و وابستگی به زمان واقعی سیستم، کنترل زمان در تست‌ها را دشوار می‌کرد. راه‌حل‌های رایج شامل ایجاد رابط‌های سفارشی (مانند ITimeService) و تزریق وابستگی (Dependency Injection) بودند. نمونه‌ای از این رویکرد را می‌توان در کد زیر مشاهده کرد:
public interface ITimeService
{
    DateTime GetCurrentTime();
}

public class SystemTimeService : ITimeService
{
    public DateTime GetCurrentTime() => DateTime.Now;
}

public class MyService
{
    private readonly ITimeService _timeService;
    public MyService(ITimeService timeService)
    {
        _timeService = timeService;
    }

    public string GetGreeting()
    {
        var hour = _timeService.GetCurrentTime().Hour;
        return hour < 12 ? "صبح بخیر" : "عصر بخیر";
    }
}
این روش اگرچه کارآمد بود، اما نیاز به کد اضافی و پیچیدگی در مدیریت وابستگی‌ها داشت. علاوه بر این، ابزارهایی مانند .NET Fakes برای شبیه‌سازی زمان وجود داشتند، اما این ابزارها تنها در نسخه Enterprise ویژوال استودیو در دسترس بودند و محدودیت‌هایی مانند وابستگی به ویندوز داشتند.

معرفی TimeProvider و قابلیت‌های آن
TimeProvider یک کلاس انتزاعی (Abstract Class) است که در .NET 8 معرفی شده و به عنوان یک جایگزین استاندارد برای مدیریت زمان عمل می‌کند. این کلاس شامل متدهایی مانند GetUtcNow برای دریافت زمان UTC و GetLocalNow برای زمان محلی، و CreateTimer برای ایجاد تایمر است. نسخه پیش‌فرض آن، یعنی TimeProvider.System، از زمان سیستم استفاده می‌کند، اما امکان ایجاد پیاده‌سازی‌های سفارشی (مانند FakeTimeProvider) برای تست وجود دارد. این ابزار به همراه بسته Microsoft.Extensions.TimeProvider.Testing ارائه می‌شود که شامل FakeTimeProvider است و به توسعه‌دهندگان اجازه می‌دهد زمان را در تست‌ها به دلخواه تنظیم کنند.
ساختار اصلی TimeProvider به این صورت است:
public abstract class TimeProvider
{
    public static TimeProvider System { get; }
    public virtual DateTimeOffset GetUtcNow();
    public DateTimeOffset GetLocalNow();
    public virtual TimeZoneInfo LocalTimeZone { get; }
    public virtual ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period);
}

استفاده از TimeProvider در کد و تست
برای استفاده از TimeProvider، کافی است آن را از طریق تزریق وابستگی به کلاس خود وارد کنید. به عنوان مثال، فرض کنید می‌خواهیم سرویسی طراحی کنیم که بر اساس ساعت روز پیام خوشامدگویی تولید کند:
public class GreetingService
{
    private readonly TimeProvider _timeProvider;
    public GreetingService(TimeProvider timeProvider)
    {
        _timeProvider = timeProvider;
    }

    public string GetGreeting()
    {
        var hour = _timeProvider.GetLocalNow().Hour;
        return hour < 12 ? "صبح بخیر" : "عصر بخیر";
    }
}
در محیط واقعی، می‌توانید TimeProvider.System را تزریق کنید. اما در تست‌ها، از FakeTimeProvider استفاده می‌کنیم تا زمان را کنترل کنیم. نمونه‌ای از تست با استفاده از xUnit:
public class GreetingServiceTests
{
    [Fact]
    public void GetGreeting_Morning_ReturnsMorningGreeting()
    {
        var fakeTimeProvider = new FakeTimeProvider();
        fakeTimeProvider.SetUtcNow(new DateTime(2025, 3, 31, 8, 0, 0, DateTimeKind.Utc)); // 8 صبح UTC
        fakeTimeProvider.SetLocalTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Iran Standard Time"));

        var service = new GreetingService(fakeTimeProvider);
        var result = service.GetGreeting();

        Assert.Equal("صبح بخیر", result);
    }

    [Fact]
    public void GetGreeting_Afternoon_ReturnsAfternoonGreeting()
    {
        var fakeTimeProvider = new FakeTimeProvider();
        fakeTimeProvider.SetUtcNow(new DateTime(2025, 3, 31, 14, 0, 0, DateTimeKind.Utc)); // 2 بعدازظهر UTC
        fakeTimeProvider.SetLocalTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Iran Standard Time"));

        var service = new GreetingService(fakeTimeProvider);
        var result = service.GetGreeting();

        Assert.Equal("عصر بخیر", result);
    }
}
در این تست‌ها، FakeTimeProvider به ما امکان می‌دهد زمان را به صورت دستی تنظیم کنیم و رفتار سرویس را در شرایط مختلف بررسی کنیم.

مدیریت تایمرها با TimeProvider
یکی از قابلیت‌های برجسته TimeProvider، امکان ایجاد و تست تایمرها با استفاده از متد CreateTimer است. این ویژگی به خصوص برای تست کدهایی که به تأخیر (Delay) یا زمان‌بندی وابسته هستند مفید است. به عنوان مثال:
public class TimerService
{
    private readonly TimeProvider _timeProvider;
    public bool HasTimerTriggered { get; private set; }

    public TimerService(TimeProvider timeProvider)
    {
        _timeProvider = timeProvider;
        _timeProvider.CreateTimer(_ => HasTimerTriggered = true, null, TimeSpan.FromSeconds(5), Timeout.InfiniteTimeSpan);
    }
}

public class TimerServiceTests
{
    [Fact]
    public void Timer_TriggersAfter5Seconds()
    {
        var fakeTimeProvider = new FakeTimeProvider();
        var startTime = new DateTime(2025, 3, 31, 10, 0, 0);
        fakeTimeProvider.SetUtcNow(startTime);

        var service = new TimerService(fakeTimeProvider);
        Assert.False(service.HasTimerTriggered);

        fakeTimeProvider.SetUtcNow(startTime.AddSeconds(6));
        Assert.True(service.HasTimerTriggered);
    }
}
این کد نشان می‌دهد که چگونه می‌توان زمان را به جلو برد و رفتار تایمر را تست کرد، بدون نیاز به انتظار واقعی برای گذشت زمان.

نتیجه‌گیری
TimeProvider در .NET 8 یک ابزار انقلابی برای توسعه‌دهندگانی است که با کدهای وابسته به زمان کار می‌کنند. این ابزار با ارائه یک راه‌حل استاندارد و منعطف، نه تنها تست واحد را ساده‌تر می‌کند، بلکه پایداری و قابلیت اطمینان برنامه‌ها را نیز افزایش می‌دهد. با استفاده از FakeTimeProvider، توسعه‌دهندگان می‌توانند سناریوهای مختلف زمانی را بدون نیاز به تغییر زمان سیستم یا انتظار واقعی شبیه‌سازی کنند.

مراجع



مشاهده مطلب اصلی