۳۰ روز با TDD: روز سوم - اولین تست شما
نوشته انگلیسی مربوط به روز سوم را در این آدرس میتوانید بخوانید. امروز میخواهیم آستینها را بالا بزنیم و اولین تست خودمان را بنویسیم. قبل از شروع لازم است به برخی ابزارها و نرمافزارهایی که به آنها در طول این دوره 30 روزه TDD احتیاج پیدا میکنیم نگاهی داشته باشیم.
اولین و مهترین ابزاری که به آن نیاز داریم Visual Studio هست. من از نسخه 2013 ویژوال استودیو برای مثالهای این سری نوشته استفاده میکنم. اگر شما از نسخه 2012 هم استفاده میکنید مشکلی نخواهید داشت. در این سری نوشتهها از [Nuget](nuget.org) استفاده زیادی خواهیم کرد، بنابراین اگر از نسخه 2008 ویژوال استودیو استفاده میکنید، این نوشته را برای راهنمایی درباره اضافه کردن امکان Nuget به VS 2008 مطالعه کنید. اگر ویژوال استدیو ندارید، میتوانید نسخه اکسپرس رایگان را از مایکروسافت دانلود کنید.
سادهترین راه برای نوشتن یک آزمون واحد (Unit Test) با #C یا VB.NET استفاده از frameworkهای آزمون واحد است. تعدادی framework برای آزمون واحد در NET. وجود دارد اما برای این سری نوشتهها من از NUnit استفاده خواهم کرد که تنها framework موجود برای Unit Test در NET. نیست اما شاید محبوبترین باشد.
در نهایت به یک اجراکننده تست (test runner) احتیاج داریم. test runner نرمافزاری است که اسمبلیهای کامپایل شده unit testهای ما را مورد بررسی قرار میدهد، به دنبال متدهایی که به عنوان test method علامتگذاری شده باشند میگردد، آن متدها را اجرا و نتیجه را گزارش میکند.
اگر NUnit را دانلود و نصب کنید، به test runner مربوط به NUnit هم دسترسی خواهید داشت که یک برنامه خارج از NET. برای اجرای تستهای NUnit است. تعدادی add-in برای ویژوال استودیو وجود دارد که تستها را از داخل خود ویژوال استودیو اجرا میکند و ما هم در این سری نوشتهها از یکی از این ابزارها استفاده خواهیم کرد. (توضیح مترجم: نویسنده اصلی قصد دارد از یکی از ابزارهای پولی شرکت تلریک به نام JustCode استفاده کند، اما ما از ابزارهای رایگان استفاده خواهیم کرد)
آماده سازی صحنه
هدف ما در این مثال این است:
"یک متد بنویسید که یک جمله و یک کاراکتر را به عنوان ورودی دریافت کند و عددی را برگرداند که مشخص کند آن کاراکتر چند بار در آن جمله استفاده شده است"
به اندازه کافی ساده به نظر میرسد. میدانیم ورودی و خروجی مورد انتظار و کاری که لازم است انجام بدهیم چیست. حالا که ابزارهای مورد نیاز را داریم و نیازمندی را هم مشخص کردیم، میتوانیم به ویژوال استودیو وارد شده و کار را شروع کنیم.
ابتدا ویژوال استودیو را باز کرده و از منو File گزینه New و بعد Project را انتخاب میکنیم. در پنجره باز شده به بخش Other Project Types رفته و مطابق شکل زیر Blank Solution را انتخاب میکنیم.

بعد از انتخاب Blank Solution یک نام به solution میدهیم. من ThirtyDaysOfTDD را انتخاب کردم (شما میتوانید نام دیگری را انتخاب کنید) و بعد هم روی Ok کلیک میکنیم. حالا مطابق شکل زیر یک solution خالی در ویژوال استودیو خواهیم داشت.

سپس باید یک پروژه برای نگهداری unit test هایم ایجاد کنم. شما باید unit test هایتان را در یک پروژه جدا از کد اصلی برنامه نگهداری کنید چرا که قرار نیست تستها را هم همراه برنامه deploy کنید. به علاوه این کار باعث مرتب بودن solution شما میشود، همچنین من integration test ها را هم در پروژه جدا از unit test ها قرار خواهم داد.
برای ایجاد یک پروژه برای unit test من در بخش Solution Explorer ویژوال استودیو روی ThirtyDaysOfTDD کلیک راست میکنم و از منوی ظاهر شده گزینه Add و بعد New Project را انتخاب میکنم و در پنجره باز شده مانند شکل زیر برای زبان #C یک Class Library به نام ThirtyDaysOfTDD.UnitTests ایجاد میکنم.

محل ایجاد پروژه را همان محل پیشفرض در نظر میگیرم و Ok میکنم. حالا باید مطابق شکل زیر پروژه برای من ایجاد شده باشد.

در پروژه ایجاد شده خود ویژوال استودیو یک کلاس به نام Class1 برای ما ایجاد کرده است. از همین کلاس استفاده خواهیم کرد اما قبل از آن نامش را به StringUtilsTests تغییر میدهیم.

بعد از این کار نوبت به اضافه کردن NUnit به پروژه میرسد. میتوانید NUnit را مستقیماً دانلود و نصب کنید و بعد به اسمبلیهای NUnit در پروژه به صورت دستی reference بدهید، اما اگر با ویژوال استودیو 2012 به بالا کار میکنید میتوانید از Nuget برای اضافه کردن NUnit به پروژه استفاده کنید.
برای گرفتن NUnit با استفاده از Nuget من به مسیر زیر در منو Solution میروم: Tools –> Library Package Manager –> Manage NuGet Packages. با کلیک بر روی Manage Nuget Packages پنجره Nuget باز میشود. در بخش Online مطابق شکل زیر دنبال Nunit بگردید (کادر جستجو در بالای سمت راست است و با وارد کردن کلمه مورد نظر و زدن Enter میتوانید جستجو کنید) بعد از چند ثانبه مطابق شکل زیر میتوانید NUnit را در نتایج جستجو مشاهده کنید.

ممکن است در نتایج جستجو، مانند شکل بالا موارد زیادی را ببینید، مورد مشخص شده را انتخاب و Install کنید. در این مرحله پنجره انتخاب پروژه باز میشود که در آن مطابق شکل زیر تنها پروژه موجود را انتخاب کنید

حالا اگر به بخش References پروژه خود بروید مطابق شکل زیر اضافه شدن اسمبلی NUnit به پروژه را میتوانید مشاهده کنید

حالا نوبت به کدنویسی میرسد :)
نوشتن اولین تست
حالا که یک پروژه داریم که اسمبلی NUnit هم به آن اضافه شده میتوانم اولین تستم را بنویسم. اما قبل از نوشتن تست، باید به NUnit بگویم که کلاس StringUtilsTests حاوی Unit test است، این کار با افزون خاصیت (Attribute) به نام TestFixture به شکل زیر انجام میشود:
توجه داشته باشید که TextFixture بخشی از کتابخانه NUnit است، بنابراین حتماً به using ابتدای کد توجه کنید. بر اساس نیازمندی که ابتدای مثال مطرح کردیم، من میتوانم خیلی راحت، تست سادهای برای سناریویی که همه ورودیها در محدوده قابل قبول قرار دارند بنویسم. برای این حالت، من تستم را اینطور تعریف میکنم:
من میخواهم جمله "!TDD is awesome" و کاراکتر "e" را پاس بدهم و خروجی باید عدد 2 باشد (یعنی کاراکتر e دو بار در آن جمله تکرار شده است) حالا باید یک متد بنویسم که کاراکتر این تست را انجام دهد.
من اسم متد را ShouldBeAbleToCountNumberOfLettersInSimpleSentence را میگذارم که هیچ پارامتر ورودی دریافت نمیکند و خروجی هم ندارد (void است) شاید اسم متد به نظر طولانی برسد ولی مشکلی نیست. توجه داشته باشید که اسم متد باید تا حد امکان تستی که میخواهد انجام دهد را مشخص کند. البته این کار همیشه ساده نیست، اما باید تا جایی که میتوانید به یک اسم خوب نزدیک شوید.
نگران طولانی بودن نام هم نباشید، چرا که شما مستقیماً متد را فراخونی نمیکنید. دیگر اینکه ترجیح من استفاده از نامهای طولانی است چرا که با بزرگ شدن برنامه و افزایش تعداد تستها، فراموش خواهم کرد که هر متد تست چه کاری انجام میدهد. داشتن یک نام خوب، وقتی این متد یکی از 500 متد تست برنامه باشد که شروع به fail کردن کرده، خیلی به من کمک خواهد کرد و بر اساس نام، احتمالاً خیلی زود میفهمم که مشکل از کجاست.
قدم بعدی این است که این متد ShouldBeAbleToCountNumberOfLettersInSimpleSentence به عنوان یک متد تست در NUnit معرفی کنیم که این کار با استفاده از خاصیت (Attribute) به نام Test انجام میشود. کد زیر را ببینید:
Arrange, Act, Assert
الگوها (Patterns) در توسعه نرمافزار خیلی به درد بخور هستند. آنها روشی برای تبدیل مشکلات پیچیده به گامهای ساده محسوب میشوند. بهترین الگوها، آنهایی هستند که وقتی به استفادهشان عادت کنید، حس طبیعی داشته باشند. وقتی نوبت به unit test میرسد الگوی Arrange, Act, Assert یا AAA تبدیل به یک استاندارد منطقی میشود.
AAA سه مرحلهای که همه unit test ها دارند را پوشش میدهد. در Arrange تست را با استفاده از ورودی (input) و خروجی مورد انتظار آماده سازی میکنیم. در تست اولی که میخواهم بنویسم این کار را با معرفی رشته ورودی نمونه و کاراکتری که دنبالش میگردم و همچنین خروجی مورد انتظار (عدد 2) به شکل زیر انجام میدهم:
علاوه بر تعریف ورودی و خروجی مورد انتظار، باید یک وهله (instance) از کلاسی که میخواهم تستش کنم هم ایجاد کنم. در اینجا اسم کلاس را StringUtils گذاشتهایم، دقت کنید که این کلاس هنوز وجود ندارد و به تعریف آن در قسمت بعدی این سری نوشته خواهیم پرداخت. کد تابع ShouldBeAbleToCountNumberOfLettersInSimpleSentence تا به حال اینطور خواهد بود:
مرحله بعد در AAA بخش Act است که جایی است که متد لازم برای تست کلاس فراخوانی شده و نتیجه آن دریافت میشود. من اسم این متد را FindNumberOfOccurences گذاشتم. دقت کنید که نه کلاس StringUtils و نه متد FindNumberOfOccurences هنوز نوشته نشدهاند و ما باید بعداً آنها را ایجاد کنیم.
و در نهایت به بخش Assert میرسیم. جایی که بررسی میکنیم که آیا نتیجهای که از تست متد کلاس StringUtils بدست آمد با نتیجه مورد انتظار ما مطابقت دارد یا خیر؟ این کار را با استفاده از Assert انجام میدهیم. کلاس Assert یک کلاس static در کتابخانه NUnit است که شامل متدهایی برای بررسی و مقایسه نتایج و اعلام pass یا fail یا بینتیجه (inconclusive) بودن تست است. برای این تست من از AreEqual استفاده میکنم که دو مقدار را با هم مقایسه میکند. اگر دو مقدار با هم برابر باشند، تست ادامه پیدا میکند. به این ترتیب امکان استفاده از چند Assert در یک متد تست وجود دارد.
اگر اجرای متد تست بدون پیغام خطا به پایان برسد، NUnit تست را pass شده فرض میکند. متدهای دیگری هم در کلاس Assert وجود دارند که در نوشتههای بعدی دربارهشان صحبت خواهم کرد.
در حال حاضر کد نهایی تست مثال این نوشته به شکل زیر خواهد بود:
در قسمت بعدی، کاری خواهیم کرد که تست pass شود. در واقع کدهای مربوط به کلاس StringUtils را تکمیل خواهیم کرد.
ادامه دارد...