تست دادهمحور در xUnit با استفاده از [InlineData]، [MemberData] و [ClassData]
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۲/۲۱ ۰۶:۵۱
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
[InlineData], [MemberData] و [ClassData] خواهیم پرداخت و نحوه استفاده از آنها را با مثالهایی مبتنی بر تست توابع تولید دنباله فیبوناچی (Fibonacci Sequence) شرح خواهیم داد. همچنین، به منظور درک بهتر تفاوتهای عملکردی، یک سنجش عملکرد (Benchmarking) ساده نیز برای مقایسه روشهای مختلف پیادهسازی تست ارائه خواهد شد.[Fact] برای تعریف تستهای واحد بدون پارامتر استفاده میکند. در مقابل، ویژگی [Theory] برای تعریف تستهای پارامتری به کار میرود. این ویژگی به xUnit.net اعلام میکند که متد تست مورد نظر باید با مجموعهای از دادههای مختلف اجرا شود. دادههای مورد نیاز برای این تستها میتوانند از طریق ویژگیهای مختلفی تامین شوند که رایجترین آنها [InlineData] است.[InlineData] زمانی مورد استفاده قرار میگیرد که مجموعه دادههای تست کوچک و ثابت باشد. این ویژگی سادهترین راه برای ارسال پارامتر به یک متد تست را فراهم میکند. اگرچه درک آن آسان است، اما بررسی آن به منظور پوشش کامل مفاهیم ضروری است.[InlineData] برای تست تابع بازگشتی (Recursive) محاسبه دنباله فیبوناچی نشان داده شده است:using Fibonacci.Library;
using System.Diagnostics;
using Xunit;
namespace Fibonacci.UnitTest;
public class FibonacciTests
{
private readonly FibonacciHelper fibonacciHelper;
public FibonacciTests()
{
this.fibonacciHelper = new FibonacciHelper();
}
[Theory]
[InlineData(0, 0)]
[InlineData(1, 1)]
[InlineData(5, 5)]
[InlineData(10, 55)]
public void Fibonacci_Recursive_InlineData(int input, int expected)
{
var watch = Stopwatch.StartNew();
int result = fibonacciHelper.Recursive(input);
watch.Stop();
Assert.Equal(expected, result);
Console.WriteLine($"[Recursive] Fibonacci({input}) took {watch.ElapsedMilliseconds} ms");
}
}[Theory] به همراه چهار نمونه [InlineData] استفاده شده است. هر [InlineData] یک مجموعه داده تست منحصربهفرد را شامل میشود که در این مورد، شامل ورودی (input) و مقدار مورد انتظار (expected) برای تابع Recursive است. در نتیجه، متد Fibonacci_Recursive_InlineData چهار بار با دادههای مختلف اجرا خواهد شد. [InlineData] برای دادههای تست کوچک و ثابت بسیار مناسب است. خروجی کنسول نشان میدهد که هر تست با ورودیهای مختلف اجرا شده و زمان اجرای هر کدام نیز ثبت شده است.[MemberData] به توسعهدهندگان اجازه میدهد تا از اعضای کلاس (فیلدها، خصوصیات یا متدهایی) که یک شیء از نوع IEnumerable<object[]> را به عنوان منبع داده تست برمیگردانند، استفاده مجدد کنند. این ویژگی به ویژه زمانی مفید است که بخواهید دادههای تست را از متد تست جدا نگه دارید یا آنها را به صورت برنامهنویسی تولید کنید.[MemberData] برای تست تابع تکراری (Iterative) محاسبه دنباله فیبوناچی را نشان میدهد:using Fibonacci.Library;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Fibonacci.UnitTest
{
public class FibonacciTest1
{
private readonly FibonacciHelper fibonacciHelper;
public FibonacciTest1()
{
this.fibonacciHelper = new FibonacciHelper();
}
public static IEnumerable<object[]> GetData(int numFiboTest)
{
var data = new List<object[]>
{
new object[] { 6, 8 },
new object[] { 7, 13 },
new object[] { 8, 21 },
new object[] { 9, 34 }
};
return data.Take(numFiboTest);
}
[Theory]
[MemberData(nameof(GetData), parameters: 4)]
public void Fibonacci_Iterative_MemberData(int input, int expected)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
int result = fibonacciHelper.Iterative(input);
watch.Stop();
Assert.Equal(expected, result);
Console.WriteLine($"[Iterative] Fibonacci({input}) took {watch.ElapsedMilliseconds} ms");
}
}
}GetData یک لیست از آرایههای شیء (object[]) را برمیگرداند که هر آرایه شامل یک ورودی و مقدار مورد انتظار برای تست است. ویژگی [MemberData] با استفاده از nameof(GetData) به این متد ارجاع میدهد و پارامتر parameters: 4 مشخص میکند که چهار مجموعه داده اول از لیست data برای تست استفاده شوند. خروجی کنسول نشان میدهد که تست Fibonacci_Iterative_MemberData چهار بار با دادههای ارائه شده توسط متد GetData اجرا شده است.[ClassData] امکان تعریف یک کلاس را فراهم میکند که رابط IEnumerable<object[]> را پیادهسازی میکند. این ویژگی به شما اجازه میدهد تا مجموعههای داده پیچیدهتری را بارگیری یا ایجاد کنید، مانند خواندن دادهها از فایلها، پایگاههای داده یا فراخوانیهای API (به صورت شبیهسازی شده). این ویژگی به ویژه برای مدیریت مجموعههای داده بزرگ یا پویا که از منابع خارجی تامین میشوند، مفید است.[ClassData] را نشان میدهد:using Fibonacci.Library;
using System.Collections;
using System.Collections.Generic;
using Xunit;
namespace Fibonacci.UnitTest;
public class AddTestDataClass : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
yield return new object[] { 11, 89 };
yield return new object[] { 12, 144 };
yield return new object[] { 13, 233 };
yield return new object[] { 14, 377 };
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class FibonacciTest2
{
private readonly FibonacciHelper fibonacciHelper;
public FibonacciTest2()
{
this.fibonacciHelper = new FibonacciHelper();
}
[Theory]
[ClassData(typeof(AddTestDataClass))]
public void Fibonacci_Iterative_ClassData(int input, int expected)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
int result = fibonacciHelper.Iterative(input);
watch.Stop();
Assert.Equal(expected, result);
Console.WriteLine($"[Iterative/ClassData] Fibonacci({input}) took {watch.ElapsedMilliseconds} ms");
}
}AddTestDataClass رابط IEnumerable<object[]> را پیادهسازی کرده و در متد GetEnumerator خود، چهار مجموعه داده تست را به صورت yield return ارائه میدهد. ویژگی [ClassData] با استفاده از typeof(AddTestDataClass) به این کلاس ارجاع میدهد. در نتیجه، متد Fibonacci_Iterative_ClassData چهار بار با دادههای تعریف شده در کلاس AddTestDataClass اجرا خواهد شد. خروجی کنسول نشان میدهد که هر تست با ورودیهای مختلف اجرا شده و زمان اجرای آنها نیز ثبت شده است.[InlineData], [MemberData] و [ClassData] به نیازها و پیچیدگی دادههای تست شما بستگی دارد.[InlineData] برای مقادیر تست کوچک و ثابت بهترین گزینه است. استفاده از آن ساده و خوانا است، اما قابلیت استفاده مجدد در تستهای مختلف را ندارد.[MemberData] ایدهآل است. این ویژگی تعادلی بین قابلیت استفاده مجدد و پیچیدگی ارائه میدهد.[ClassData] بیشترین انعطافپذیری را برای سناریوهای پیشرفتهتر، مانند بارگیری مجموعههای داده بزرگ از منابع خارجی (فایلها، APIها یا پایگاههای داده) فراهم میکند. اگرچه تنظیمات آن پیچیدهتر است، اما امکان جداسازی منطق داده و ارتقاء قابلیت استفاده مجدد در سراسر مجموعههای تست را فراهم میسازد.