عنوان:

‫راهنمای مهاجرت به xUnit v3


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۶/۲۶ ۰۹:۲۵
آدرس: www.dntips.ir
تیم توسعه‌دهنده xUnit با عرضه نسخه ۳، یک بازنگری اساسی و ساختاری در این فریمورک محبوب تست‌نویسی ایجاد کرده است. این نسخه جدید با بسته‌هایی کاملاً متفاوت عرضه شده و هیچ‌گونه سازگاری با نسخه‌های قدیمی‌تر (Backward Compatibility) ندارد. این تغییر یک شیفت معماری بزرگ است که هدف آن بهبود عملکرد، ایزوله‌سازی بهتر تست‌ها و ارائه APIهای مدرن به دنیای تست در دات‌نت است. اگرچه فرآیند مهاجرت نیازمند تغییراتی در پروژه‌های شماست، اما مزایای آن این زحمت را ارزشمند می‌کند. در این مقاله، ابتدا به تغییرات کلیدی و ویژگی‌های جدید می‌پردازیم و سپس راهنمای گام‌به‌گام مهاجرت از نسخه ۲ به ۳ را بررسی می‌کنیم.

۱. مهم‌ترین تغییر معماری: از کتابخانه (Library) به فایل اجرایی (Executable)

شاید بزرگ‌ترین و بنیادی‌ترین تغییر در xUnit v3، تبدیل پروژه‌های تست از Class Library به اپلیکیشن‌های اجرایی مستقل باشد. این رویکرد جدید بسیاری از مشکلات موجود در نسخه ۲ را برطرف می‌کند:
  • حل وابستگی‌ها (Dependency Resolution): در نسخه جدید، تمام وابستگی‌ها در زمان کامپایل (Build Time) توسط کامپایلر حل می‌شوند، نه در زمان اجرا (Runtime). این امر از بروز بسیاری از خطاهای مرتبط با بارگذاری Assemblyها در زمان اجرای تست‌ها جلوگیری می‌کند.
  • ایزوله‌سازی فرآیندها (Process Isolation): تست‌ها اکنون در فرآیندهای مجزا اجرا می‌شوند که ایزوله‌سازی بسیار قوی‌تری نسبت به رویکرد Application Domain در نسخه ۲ فراهم می‌کند. این یعنی تست‌ها کمتر روی یکدیگر تأثیر منفی می‌گذارند.
  • اجرای ساده و مستقیم: شما می‌توانید فایل خروجی پروژه تست خود را مستقیماً اجرا کنید و دیگر نیازی به ابزارهای جانبی (Runner) برای اجرای تست‌ها ندارید.

وقتی یک پروژه تست مبتنی بر xUnit v3 را بیلد می‌کنید، خروجی به شکل زیر خواهد بود:
  • برای ‎.NET Framework: یک فایل .exe که مستقیماً تست‌ها را اجرا می‌کند.
  • برای ‎.NET (Core): یک فایل .dll حاوی تست‌ها به همراه یک فایل راه‌انداز (Stub) با پسوند .exe.
این تغییر همچنین پروژه شما را از وابستگی به Microsoft.NET.Test.Sdk جدا می‌کند، هرچند اگر همچنان بخواهید از VSTest Runner (مثلاً در Visual Studio یا محیط‌های CI/CD) استفاده کنید، می‌توانید با اضافه کردن پکیج xunit.runner.visualstudio این قابلیت را حفظ کنید.

۲. ویژگی‌های جدید و کاربردی در xUnit v3

علاوه بر تغییر معماری، نسخه ۳ قابلیت‌های جدیدی را نیز به ارمغان آورده است.

دریافت خروجی‌های Console، Trace و Debug

یکی از قابلیت‌هایی که جای خالی آن حس می‌شد، امکان مشاهده خروجی‌های Console.WriteLine، Debug.WriteLine و Trace.WriteLine در نتیجه تست بود. این ویژگی که به دیباگ کردن تست‌های ناموفق کمک شایانی می‌کند، در نسخه ۳ اضافه شده است.
این قابلیت به طور پیش‌فرض برای حفظ سازگاری غیرفعال است، اما شما می‌توانید با افزودن Attributeهای زیر در سطح Assembly (مثلاً در فایل AssemblyInfo.cs یا هر فایل سی‌شارپ دیگر) آن را فعال کنید:
// این Attribute خروجی‌های Console.WriteLine را ثبت می‌کند
[assembly: Xunit.CaptureConsole]

// این Attribute خروجی‌های Debug.WriteLine و Trace.WriteLine را ثبت می‌کند
[assembly: Xunit.CaptureTrace]

به اشتراک‌گذاری یک Fixture بین تمام کلاس‌های تست (Assembly Fixture)

در نسخه‌های قبلی، برای به اشتراک‌گذاری وضعیت (State) بین تست‌ها از ClassFixture (برای یک کلاس) یا CollectionFixture (برای مجموعه‌ای از کلاس‌ها) استفاده می‌کردیم. اما اگر نیاز داشتید یک نمونه از یک کلاس سنگین (مانند یک دیتابیس در Docker یا یک وب سرور) را یک بار برای کل پروژه تست ایجاد کنید، راه حل ساده‌ای وجود نداشت.
xUnit v3 با معرفی AssemblyFixture این مشکل را حل کرده است. xUnit تنها یک نمونه از این Fixture را برای تمام تست‌های موجود در Assembly ایجاد کرده و در اختیار همه آن‌ها قرار می‌دهد.
مثال زیر نحوه استفاده از آن را برای یک دیتابیس تستی نشان می‌دهد:
// AssemblyFixture را در سطح Assembly تعریف می‌کنیم
[assembly: Xunit.AssemblyFixture(typeof(DatabaseFixture))]

// کلاس فیکسچر که باید یک بار ساخته و در پایان از بین برود
public class DatabaseFixture : IDisposable
{
    public SqlConnection Db { get; private set; }

    public DatabaseFixture()
    {
        Db = new SqlConnection("MyConnectionString");
        // ... کد مربوط به راه‌اندازی و مقداردهی اولیه دیتابیس ...
    }

    public void Dispose()
    {
        // ... کد مربوط به پاک‌سازی دیتابیس پس از اتمام تمام تست‌ها ...
        Db.Dispose();
    }
}

// کلاس تست اول که از فیکسچر استفاده می‌کند
public class DatabaseTestClass1
{
    private readonly DatabaseFixture _fixture;

    // فیکسچر از طریق Constructor تزریق می‌شود
    public DatabaseTestClass1(DatabaseFixture fixture)
    {
        _fixture = fixture;
    }

    [Fact]
    public void Test1()
    {
        // استفاده از _fixture.Db
    }
}

// کلاس تست دوم نیز از همان نمونه فیکسچر استفاده می‌کند
public class DatabaseTestClass2
{
    private readonly DatabaseFixture _fixture;

    public DatabaseTestClass2(DatabaseFixture fixture)
    {
        _fixture = fixture;
    }
    // ...
}

۳. راهنمای گام‌به‌گام مهاجرت

حالا که با تغییرات کلیدی آشنا شدیم، زمان مهاجرت فرا رسیده است. قبل از شروع، پیش‌نیازها را بررسی کنید.

پیش‌نیازها

  • نسخه‌های دات‌نت: xUnit v3 به حداقل نسخه‌های زیر نیاز دارد:
  • .NET Framework 4.7.2 یا بالاتر
  • .NET 8 یا بالاتر
  • پروژه‌های SDK-style: پروژه تست شما حتماً باید از فرمت جدید و مدرن SDK-style (.csproj) استفاده کند. اگر هنوز از فرمت قدیمی استفاده می‌کنید، ابتدا باید پروژه خود را به این فرمت تبدیل کنید.

مرحله ۱: به‌روزرسانی پکیج‌های NuGet

اولین قدم، جایگزینی پکیج‌های قدیمی با نسخه‌های جدید v3 است. نام پکیج‌ها تغییر کرده و اغلب پیشوند xunit.v3 گرفته‌اند.
پکیج‌های قدیمی (v2) را حذف کنید:
<PackageReference Include="xunit" Version="2.x.x" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.x.x" />
<PackageReference Include="xunit.analyzers" Version="0.x.x" />
و پکیج‌های جدید (v3) را جایگزین کنید:
<PackageReference Include="xunit.v3" Version="0.x.x" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.x.x" />
<PackageReference Include="xunit.analyzers" Version="1.x.x" />
نکته مهم: پکیج xunit.abstractions دیگر مورد نیاز نیست و باید به طور کامل از پروژه حذف شود.

مرحله ۲: تبدیل پروژه به فایل اجرایی

فایل .csproj پروژه تست خود را باز کرده و تگ را به آن اضافه کنید تا خروجی پروژه به Exe تغییر کند.
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <OutputType>Exe</OutputType> <IsPackable>false</IsPackable>
  </PropertyGroup>

  </Project>

مرحله ۳: بیلد و اجرای تست‌ها

پس از انجام تغییرات بالا، پکیج‌ها را Restore کرده و پروژه را بیلد کنید:
dotnet restore
dotnet build
اگر بیلد با موفقیت انجام شد، اکنون می‌توانید تست‌های خود را به روش‌های جدید اجرا کنید:
# اجرای مستقیم فایل اجرایی
./bin/Debug/net8.0/YourTestProject.exe

# یا اجرای از طریق دستور dotnet run
dotnet run

# می‌توانید آرگومان‌های خط فرمان xUnit را نیز به آن پاس دهید
dotnet run -- -xml results.xml

۴. نکات و بهترین شیوه‌ها (Best Practices) در v3

  • استفاده از ValueTask برای تست‌های async: برای بهبود عملکرد در تست‌های آسنکرون، توصیه می‌شود به جای Task از ValueTask استفاده کنید. این کار در سناریوهایی که متد به صورت همزمان (Synchronously) کامل می‌شود، از تخصیص حافظه روی Heap جلوگیری می‌کند.
[Fact]
public async ValueTask MyAsyncTest()
{
    // ...
}
  • بهره‌گیری از Assertionهای جدید: نسخه ۳ شامل متدها و overloadهای جدیدی برای Assert است. مستندات رسمی را برای آشنایی با آن‌ها بررسی کنید.
  • سازماندهی بهینه تست‌ها: با مدل اجرایی جدید، می‌توانید پروژه‌های تست بزرگ را به چندین پروژه اجرایی کوچک‌تر تقسیم کنید. این کار به شما امکان موازی‌سازی بهتر و مدیریت ساده‌تر تست‌ها را می‌دهد.
  • تأثیر بر روی CI/CD: در پایپ‌لاین‌های CI/CD، قبلاً از دستور dotnet test استفاده می‌کردید. این دستور همچنان با وجود پکیج xunit.runner.visualstudio کار می‌کند. اما اکنون یک گزینه جدید و قدرتمند دیگر نیز دارید: اجرای مستقیم فایل .exe خروجی. این روش می‌تواند سریع‌تر باشد و کنترل بیشتری بر روی فرآیند اجرای تست‌ها به شما بدهد.

جمع‌بندی

مهاجرت به xUnit v3 یک گام رو به جلو برای مدرن‌سازی فرآیند تست‌نویسی در اکوسیستم دات‌نت است. اگرچه این تغییر نیازمند صرف زمان است، اما مزایایی چون عملکرد بالاتر، ایزوله‌سازی قوی و سادگی در اجرا، آن را به یک سرمایه‌گذاری ارزشمند برای پروژه‌های شما تبدیل می‌کند.