بهترین الگوهای آزمایش واحد در NET Core. و NET Standard. قسمت دوم

تست هایی بنویسید که به ساده ترین صورت پاس میشوند
ورودی برای استفاده در آزمون واحد باید ساده ترین حالت ممکن باشد تا بتوانید رفتاری را که در حال حاضر آزمایش می کنید تأیید کنید.
چرا؟
آزمایشات در برابر تغییرات بعدی در کد ، انعطاف پذیرتر می شوند.
آزمونهایی که اطلاعات بیشتری از حد لازم برای قبولی در آزمون را شامل می شوند ،
شانس بیشتری برای ورود خطا به آزمون دارند و می توانند هدف آزمون را کمتر روشن کنند.
هنگام نوشتن تست ، بر روی رفتار تمرکز کنید. تنظیم ویژگیهای اضافی روی مدلها در صورت عدم نیاز ، فقط چیزی را که می خواهید اثبات کنید از بین می برد.
نمونه بد
[Fact]
public void Add_SingleNumber_ReturnsSameNumber()
{
var stringCalculator = new StringCalculator();
var actual = stringCalculator.Add("42");
Assert.Equal(42, actual);
}
نمونه بهتر
[Fact]
public void Add_SingleNumber_ReturnsSameNumber()
{
var stringCalculator = new StringCalculator();
var actual = stringCalculator.Add("0");
Assert.Equal(0, actual);
}
از رشته های جادویی پرهیز کنید
نامگذاری متغیرها در آزمون های واحد به اندازه نام گذاری متغیرها در کد تولید مهم می باشد. تست های واحد نباید شامل رشته های جادویی باشد.
چرا؟
رشته های جادویی باعث سردرگمی خواننده آزمایش های شما می شوند. اگر رشته ای غیرعادی به نظر برسد ، ممکن است تعجب کنند که چرا مقدار خاصی برای پارامتر ورودی یک تابع یا مقدار برگشتی آن انتخاب شده است و باعث می شود آنها به جای تمرکز روی آزمون ، جزئیات کد را از نزدیک بررسی کنند.
- رشته های جادویی مقادیر رشته ای هستند که مستقیماً در کد برنامه مشخص می شوند و بر رفتار برنامه تأثیر دارند. غالباً ، در پایان این رشته ها در سیستم تکرار می شوند و از آنجا که نمی توانند با استفاده از ابزارهای بازسازی مجدد (refactoring tools) به طور خودکار به روز شوند، در صورت ایجاد تغییر در برخی از رشته ها ، آنها به یک منبع از اشکالات تبدیل می شوند.
هنگام نوشتن آزمون ها ، باید هدف خود را به خوبی بیان کنید. یک روش خوب در مورد رشته های جادویی ، اختصاص دادن این مقادیر به ثابت ها است.
نمونه بد
[Fact]
public void Add_BigNumber_ThrowsException()
{
var stringCalculator = new StringCalculator();
Action actual = () => stringCalculator.Add("1001");
Assert.Throws<OverflowException>(actual);
}
نمونه بهتر
[Fact]
void Add_MaximumSumResult_ThrowsOverflowException()
{
var stringCalculator = new StringCalculator();
const string MAXIMUM_RESULT = "1001";
Action actual = () => stringCalculator.Add(MAXIMUM_RESULT);
Assert.Throws<OverflowException>(actual);
}
از نوشتن عبارت های منطقی در تست پرهیز کنید
هنگام نوشتن تست های واحد خود ، از عبارت های شرطی و منطقی مانند if
, while
, for
, switch
و غیره خودداری کنید.
چرا
- در این صورت شانس کمتری برای به وجود آمدن خطا در آزمون ها وجود دارد.
- به جای جزئیات اجرا ، بر روی نتیجه نهایی تمرکز کنید.
وقتی عبارت های منطقی را به مجموعه آزمون های خود وارد می کنید ، احتمال ورود خطا به آن به طور چشمگیری افزایش می یابد. آخرین مکانی که احتمال وجود یک اشکال در آن است باید در آزمون های شما باشد. شما باید مطمئن باشید که آزمایشات شما جواب می دهد . تست هایی که به آنها اعتماد ندارید ، هیچ ارزشی ندارند. وقتی آزمایشی ناموفق بود ، می خواهید این تصور را داشته باشید که در واقع کد شما اشتباه است و اشکال کار از خود آزمون نیست.
اگر استفاده عبارت های منطقی در آزمون شما اجتناب ناپذیر به نظر می رسد ، آزمون خود را به دو یا چند آزمون مختلف تقسیم کنید.
نمونه بد
[Fact]
public void Add_MultipleNumbers_ReturnsCorrectResults()
{
var stringCalculator = new StringCalculator();
var expected = 0;
var testCases = new[] { "0,0,0", "0,1,2", "1,2,3" };
foreach (var test in testCases)
{
Assert.Equal(expected, stringCalculator.Add(test));
expected += 3;
}
}
نمونه بهتر
[Theory]
[InlineData("0,0,0", 0)]
[InlineData("0,1,2", 3)]
[InlineData("1,2,3", 6)]
public void Add_MultipleNumbers_ReturnsSumOfNumbers(string input, int expected)
{
var stringCalculator = new StringCalculator();
var actual = stringCalculator.Add(input);
Assert.Equal(expected, actual);
}
از نوشتن چند Assert در یک آزمون خودداری کنید
هنگام نوشتن آزمون های خود ، سعی کنید فقط یک assert در هر آزمون داشته باشید.
راه حل ها:
- برای هر assert یک آزمون جداگانه ایجاد کنید.
- از آزمون های دارای پارامتر استفاده کنید.
چرا؟
- اگر یکی از assert ها خراب شود ، assert های بعدی ارزیابی نمی شوند.
- اطمینان می دهد که قسمت های مختلف کد را در داخل یه آزمون تست نمی کنید.
- دلیل شکست آزمون شما را به صورت شفاف نشان میدهد.
هنگام نوشتن چندین assert در یک مورد آزمایشی ، تضمین نمی شود که همه assert ها اجرا شوند. در بیشتر چارچوب های تست واحد ، هرگاه assert در یک آزمون واحد با شکست روبرو شود ، آزمون های آزمایشی به طور خودکار بی نتیجه تلقی می شوند. این می تواند گیج کننده باشد زیرا عملکردی که در واقع کار می کند ، نشان داده می شود که خراب است.
نمونه بد
[Fact]
public void Add_EdgeCases_ThrowsArgumentExceptions()
{
Assert.Throws<ArgumentException>(() => stringCalculator.Add(null));
Assert.Throws<ArgumentException>(() => stringCalculator.Add("a"));
}
نمونه بهتر
[Theory]
[InlineData(null)]
[InlineData("a")]
public void Add_InputNullOrAlphabetic_ThrowsArgumentException(string input)
{
var stringCalculator = new StringCalculator();
Action actual = () => stringCalculator.Add(input);
Assert.Throws<ArgumentException>(actual);
}
اعتبار سنجی توابع خصوصی با آزمون واحد
در بیشتر موارد ، نیازی به آزمایش توابع خصوصی نیست. توابع خصوصی از جزئیات پیاده سازی می باشند. شما می توانید به این روش فکر کنید که توابع خصوصی هرگز به صورت تنها مفهومی ندارند. در نهایت تابع خصوصی توسط یک تابع عمومی فراخوانی خواهد شد و شما می توانید توسط تابع عمومی آن را تست کنید
کد زیر را در نظر بگیرید
public string ParseLogLine(string input)
{
var sanitizedInput = TrimInput(input);
return sanitizedInput;
}
private string TrimInput(string input)
{
return input.Trim();
}
public void ParseLogLine_StartsAndEndsWithSpace_ReturnsTrimmedResult()
{
var parser = new Parser();
var result = parser.ParseLogLine(" a ");
Assert.Equals("a", result);
}
با این دیدگاه ، اگر یک تابع خصوصی می بینید ، تابع عمومی را پیدا کنید و آزمون های خود را برای آن تابع بنویسید. دقت کنبد چون که یک تابع خصوصی نتیجه مورد انتظار را برمی گرداند ، به این معنی نیست که سیستمی که تابع خصوصی را فراخوانی می کند ، از نتیجه به درستی استفاده می کند.
از بین بردن منابع Static
یکی از اصول آزمون واحد این است که باید کنترل کاملی روی سیستم مورد آزمایش داشته باشد. وقتی کد تولید شامل فراخوانی منابع استاتیک (به عنوان مثال DateTime.Now) باشد می تواند مشکل ساز باشد.
به موارد زیر توجه کنید :
public int GetDiscountedPrice(int price)
{
if (DateTime.Now.DayOfWeek == DayOfWeek.Tuesday)
{
return price / 2;
}
else
{
return price;
}
}
چگونه می توان برای این کد واحد آزمون نوشت ؟ ممکن است شما مانند کد زیر عمل کنید:
public void GetDiscountedPrice_NotTuesday_ReturnsFullPrice()
{
var priceCalculator = new PriceCalculator();
var actual = priceCalculator.GetDiscountedPrice(2);
Assert.Equals(2, actual)
}
public void GetDiscountedPrice_OnTuesday_ReturnsHalfPrice()
{
var priceCalculator = new PriceCalculator();
var actual = priceCalculator.GetDiscountedPrice(2);
Assert.Equals(1, actual);
}
متأسفانه ، شما به سرعت متوجه خواهید شد که در آزمون شما چند مشکل وجود دارد.
اگر آزمون در روز سه شنبه اجرا شود ، آزمون دوم قبول می شود ، اما آزمون اول ناموفق است.
اگر آزمون در هر روز دیگری اجرا شود ، آزمون اول قبول می شود ، اما آزمون دوم با شکست مواجه می شود.
برای حل این مشکلات ، شما باید یک تغییر در کد تولید خود ایجاد کنید. یک روش این است که یک اینترفیس بنویسید و کد تولید خود را پشت آن پنهان کنید.
public interface IDateTimeProvider
{
DayOfWeek DayOfWeek();
}
public int GetDiscountedPrice(int price, IDateTimeProvider dateTimeProvider)
{
if (dateTimeProvider.DayOfWeek() == DayOfWeek.Tuesday)
{
return price / 2;
}
else
{
return price;
}
}
آزمون شما به شکل زیر تغییر می کند:
public void GetDiscountedPrice_NotTuesday_ReturnsFullPrice()
{
var priceCalculator = new PriceCalculator();
var dateTimeProviderStub = new Mock<IDateTimeProvider>();
dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Monday);
var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);
Assert.Equals(2, actual);
}
public void GetDiscountedPrice_OnTuesday_ReturnsHalfPrice()
{
var priceCalculator = new PriceCalculator();
var dateTimeProviderStub = new Mock<IDateTimeProvider>();
dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Tuesday);
var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);
Assert.Equals(1, actual);
}
دیدگاهتان را بنویسید