این که ما برنامه نویسان به رویکرد Test Driven Development و نوشتن Unit Test عادت می کنیم اتفاق بسیار خوبی است. اما باید بدانیم که هدف ما از انجام این کار چیست؟
بدون شک هر تستی باید سه ویژگی اصلی داشته باشد که عبارتند از (Maintainability) قابل نگهداری و (Trust Worthy) قابل اطمینان و (Readability) خوانایی که این آخرین ویژگی مهمترین آنها است. اما چرا Readability تست ها مهمترین ویژگی یک تست خوب است؟
همه می دانیم که هدف از تست و رویکرد TDD دریافت بازخورد سریع (Fast Feedback) و پیدا کردن محل دقیق باگ (Defect Localization) و ایجاد مستندات زنده و به روز از نحوه کارکرد سیستم (Living Documentation)است. حال اگر تست های ما خوانایی و وضوح لازم در بیان آنچه انجام می دهند نداشته باشند آیا دستیابی به هدف Living Documentation امکان پذیر است؟ قطعا خیر
مفهوم Living Documentation
Living Documentation یعنی تست های ما بیانگر نحوه عملکرد سیستم تحت تست هستند که به طور مداوم همراه با کد تغییر می کنند پس نیازی به نوشتن مستندات سنتی در سطح کد نیست. تست ها بیانگر همه چیز هستند.
مفهوم Defect Localization
Defect Localization یعنی پیدا کردن باگ در کوتاهترین زمان ممکن. اغلب پیدا کردن مکان وقوع باگ در سیستم کار دشوارتری نسبت به رفع آن است. اگر کدهای ما تست داشته باشند می توانیم به محض وقوع یک باگ به راحتی محل دقیق باگ را در کدها پیدا کنیم.
صحبت پیرامون Readability بحث بسیار گسترده ای است که در این مقاله قصد داریم به بخشی از آن بپردازیم. به تست زیر دقت کنید:
[Fact]
public void TestConstructorFailure()
{
//Arrange
const string name = null;
const int health = 100;
const int damage = 10;
//Assert
Assert.Throws<NullReferenceException>(() => new Hero(name, health, damage));
}
آیا در نگاه اول با خواندن نام این تست می توان فهمید که این متد چه چیز را تست می کند؟ چه ویژگی تحت تست است؟ کدام متد درگیر تست است؟ چه شرایطی باعث Failure می شود؟
تمام این موارد را می توان با انتخاب نامی مناسب برای تست در نظر گرفت و باعث افزایش خوانایی تست شد. برای این کار بهتر است از یک الگوی نام گذاری استفاده کنیم. الگو های زیادی برای نام گذاری تست ها وجود دارد اما آن الگوی مورد نظر ما که تمام ایرادات مطرح شده را برطرف می کند به شکل زیر است:
[Fact]
public void MethodUnderTest_Should_ExpectedBehavior_When_StateUnderTest()
{
}
حال می خواهید تست بالا را از نظر نام گذاری Refactor کنیم. می دانیم در شرایطی که مقدار name به Constructor تحویل داده می شود null باشد باید خطای NullReferenceException دریافت کنیم. بنابراین نام تست به شکل زیر خواهد شد:
[Fact]
public void Constructor_Should_Throw_Exception_When_Name_Is_Null()
{
//Arrange
const string name = "";
const int health = 100;
const int damage = 10;
//Assert
Assert.Throws<NullReferenceException>(() => new Hero(name, health, damage));
}
با این تغییر به ظاهر کوچک میزان Readability تست های ما به میزان قابل توجهی افزایش می یابد. حال کاملا می دانید که این تست دقیقا چه کار می کند.