انجام Unit Testing در سی شارپ و .Net Core با NUnit

پرووید

NUnit2

در این آموزش از وبسایت پرووید در رابطه با انجام unit testing در .NET Core و با استفاده از NUnit صحبت خواهیم کرد. در ابتدا بگوییم که نسخه ی دیگر این آموزش با استفاده از فریم ورک MSTest و همچنین فریم ورک xUnit پیش از این بر روی سایت پرووید منتشر شده اند. ضمناً می توانید فایل های پروژه ی این آموزش را نیز از این آدرس دانلود کنید. علاوه بر این، توصیه می کنیم که حتماً از آموزش تست واحد Unit Testing در سی شارپ و آموزش توسعه تست محور Test Driven Development پیشرفته در سی شارپ و همچنین از آموزش ساخت برنامه های Cross-Platform با .NET Core و آموزش .NET Core برای برنامه نویسان دیدن کنید. خب بیایید با هم بحث را آغاز کنیم.

ساختن پروژه ی source

کار را با dot net CLI آغاز می کنیم. لطفاً، یک فولدر با نام unit-testing-using-nunit برای قرار دادن solution در آن ایجاد کنید. در این فولدر دستور dotnet new sln را اجرا کنید تا یک فایل solution جدید ایجاد شود. این فایل solution در ادامه حاوی یک class library و یک test project خواهد بود. پس از آن یک فولدر با نام PrimeService ایجاد کنید. در حال حاضر ساختار فایل ها و فولدرهایی که تا اینجا ایجاد کرده اید را مشاهده می کنید.

فولدر PrimeService را به عنوان current directory تنظیم کرده و دستور dotnet new classlib را اجرا کنید. این دستور باعث ساخته شدن یک class library جدید می شود. فایل Class1.cs را به PrimeService.cs تغییر نام دهید.

خب، اگر با مفاهیم test-driven development آشنا باشید، می دانید که در ابتدا باید test ای را بنویسیم که fail شود، پس از آن با پیاده سازی logic مورد نظر کاری کنیم که آن تست pass شود و بعد از نیز refactoring هایی را بر روی logic پیاده سازی شده انجام دهیم. برای اطلاعات بیشتر در مورد ریفکتورینگ توصیه می کنیم که از آموزش ریفکتورینگ Refactoring در سی شارپ استفاده کنید. ضمناً، برای اطلاعات بیشتر در مورد unit testing و test-driven development می توانید از آموزش تست واحد Unit Testing در سی شارپ و همچنین آموزش توسعه تست محور Test First Development در سی شارپ سری اول و آموزش توسعه تست محور Test First Development در سی شارپ سری دوم استفاده کنید.

خب، کدی که در قسمت زیر مشاهده می کنید محتویات کلاس PrimeService را نشان می دهد.

 
using System;

namespace Prime.Services
{
    public class PrimeService
    {
        public bool IsPrime(int candidate)
        {
            throw new NotImplementedException("Please create a test first");
        }
    }
}

همانطور که مشاهده می کنید برای متد IsPrime که یک پارامتر ورودی از نوع int دریافت کرده است هیچ logic ای در نظر گرفته نشده است. این موضوع بدون شک باعث fail شدن اولین test ما خواهد شد. اما نگران نباشید. با استفاده از همان سیکل سه مرحله ای که در قسمت بالا نیز به آن اشاره کردیم و تحت عنوان Red-Green-Refactor نیز شناخته می شود، پیاده سازی مناسبی را برای بدنه ی این متد در نظر خواهیم گرفت.

با استفاده از CLI به فولدر unit-testing-using-mstest بروید و دستور dotnet sln add PrimeService/PrimeService.csproj را اجرا کنید. این دستور باعث می شود که پروژه ی class library ای که ایجاد کردیم به solution اضافه شود.

ساخت پروژه ی test

در ابتدا یک فولدر با نام PrimeService.Tests ایجاد کنیم. در حال حاضر ساختار پروژه و فایل های آن شبیه به ساختار زیر می باشد.

این فولدر ساخته شده را به عنوان current directory تنظیم کنید و با استفاده از دستور dotnet new nunit یک پروژه ی test project بسازید. این دستور باعث ساخته شدن یک پروژه ی test جدید می شود که از NUnit به عنوان test library استفاده می کند. ضمناً، اگر نگاهی به فایل PrimeServiceTests.csproj در یک xml editor بیندازید می بینید که به صورت خودکار test runner نیز برای ما پیکربندی شده است. این موضوع در کد زیر نشان داده شده است.

 

  
  
  

خب، پروژه ی test ای که تا به اینجا ایجاد کرده ایم نیاز به پکیج های دیگری نیز دارد تا بتواند به درستی کار کند. خب، در قسمت قبل با استفاده از دستور dotnet new توانستیم مواردی از قبیل Microsoft test SDK، NUnit test framework و همچنین NUnit test adapter را به پروژه اضافه کنیم.

در این قسمت باید پروژه ی PrimeService را به عنوان یک dependency به پروژه ی test اضافه کنیم. اگر مایل باشید می توانید از آموزش معکوس سازی کنترل Inversion of Control در سی شارپ وبسایت پرووید که در رابطه با یک از مهمترین موضوع ها در توسعه ی نرم افزار تنظیم شده اند استفاده کنید. به منظور اضافه کردن پروژه ی PrimeService به پروژه ی test به عنوان یک dependency از دستور dotnet add reference به شکل dotnet add reference ../PrimeService/PrimeService.csproj استفاده می کنیم.

ساختار زیر نشان دهنده ی ساختار فایل ها و فولدرهای پروژه تا اینجای کار می باشد. دستور dotnet sln add .PrimeService.TestsPrimeService.Tests.csproj را در فولدر unit-testing-using-dotnet-test اجرا کنید.

ساختن اولین test

همانطور که در قسمت های قبلی این آموزش گفتیم در روش test-driven development ابتدا اقدام به نوشتن test ای می کنیم که fail می شود، بعد با پیاده سازی logic مورد نظر آن test باعث می شویم که test نوشته شده pass شود و بعد هم بر روی logic ای که پیاده سازی شده است refactoring انجام می دهیم.

خب، به منظور ساختن اولین test، ابتدا فایل UnitTest1.cs را از فولدر PrimeService.Tests حذف کرده و یک فایل جدید سی شارپ با نام PrimeService_IsPrimeShould.cs ایجاد کرده و کد زیر را در آن قرار دهید.

 
using NUnit.Framework;
using Prime.Services;

namespace Prime.UnitTests.Services
{
    [TestFixture]
    public class PrimeService_IsPrimeShould
    {
        private readonly PrimeService _primeService;

        public PrimeService_IsPrimeShould()
        {
            _primeService = new PrimeService();
        }

        [Test]
        public void ReturnFalseGivenValueOf1()
        {
            var result = _primeService.IsPrime(1);

            Assert.IsFalse(result, "1 should not be prime");
        }
    }
}

همانطور که در این فایل مشاهده می کنید یک attribute با نام [TestFixture] بر روی کلاس اضافه شده است که مشخص می کند که این فایل حاوی unit test هایی می باشد. علاوه بر این، یک attribute دیگر با نام [Test] بر روی هر متد که در نقش یک test method می باشد اضافه شده است.

در ادامه فایل را save کنید و با استفاده از دستور dotnet test اقدام به build کردن test ها و همچنین class library کنید و test ها را run کنید. باید بدانید که test runner مربوط به NUnit حاوی entry point یا همان نقطه ی ورود برنامه برای run کردن test ها می باشد. دستور dotnet test باعث اجرا شدن test runner با استفاده از پروژه ی unit test ای می شود که تا به اینجا ایجاد کرده اید.

بدون شک در این قسمت test شما fail می شود ولی نگران نباشید. سیکلی که در قسمت های قبلی این آموزش از آن صحبت کردیم را به یاد بیاورید: Red-Green-Refactor. ما تا به اینجا قسمت اول را انجام داده ایم. یعنی test ای را نوشته ایم که fail می شود. هم اکنون زمان مرحله ی دوم یعنی Green می باشد. به عبارت دیگر، پیاده سازی logic مورد نظری که باعث pass شدن test ی شود که در حال حاضر fail می شود.

خب، به منظور pass شدن test مورد نظر logic زیر را برای متد IsPrime از پروژه ی PrimeService لحاظ کنید.

 
public bool IsPrime(int candidate)
{
    if (candidate == 1)
    {
        return false;
    }
    throw new NotImplementedException("Please create a test first");
}

پس از این در فولدر unit-testing-using-nunit، با استفاده از دستور dotnet test اقدام به build کردن پروژه ی PrimeService و سپس PrimeService.Tests کنید. پس از build شدن پروژه ها، اولین و تنهاترین test ما تا به اینجا اجرا شده و pass می شود. پس توانستیم مرحله ی دوم از آن سیکل سه مرحله ای یعنی Green را نیز انجام دهیم.

اگر هم اکنون در حال فکر کردن به مرحله ی سوم سیکل یعنی انجام ریفکتورینگ هستید، باید بگوییم که بدنه ی متد IsPrime به قدری ساده است که در حال حاضر نیاز به انجام ریفکتورینگ ندارد. ریفکتورینگ موضوع بسیار مهمی است که توصیه می کنیم با استفاده از آموزش ریفکتورینگ Refactoring در سی شارپ در سی شارپ اقدام به یادگیری آن کنید.

افزودن قابلیت های دیگر

حال که اولین test برنامه را نوشتیم، می توانیم به سناریوهای دیگری نیز فکر کنیم و test های دیگری نیز پیاده سازی کنیم. برای مثال، برای اعداد اول که پروژه ی ما در مورد آنها می باشد (دقت کنید که کلمه ی انگلیسی Prime Number به معنی عدد اول است.)، موارد دیگری مثل 1 و -1 نیز وجود دارد. خب ممکن است فکر کنید که سریعاً برای این دو مورد نیز تعدادی test method اضافه کنیم. این کار شدنی است ولی مشکلی که ایجاد می کنید حجم بالای کدهای درون test class مان می باشد. ما می توانیم با قابلیت های فریم ورک nUnit اقدام به نوشتن test هایی کنیم که تا حدودی با هم مشابه هستند ولی پارامترهای ورودی مختلفی دارند.

شاید بدانید که attribute ای با نام [TestCase] به منظور نوشتن test method هایی که یک کد یکسان را test می کنند ولی پارامترهای ورودی متفاوتی دارند استفاده می شود. پس می توانیم به جای نوشتن test method های اضافه این attribute را به یک test تک اعمال کنیم. به این نوع test ها اصطلاحاً data driven test می گوییم. دقت کنید که پیاده سازی همین موضوع با استفاده از فریم ورک MSTest و فریم ورک xUnit هم پیش از این بر روی وبسایت پرووید منتشر شده است. کدی که در قسمت زیر آمده است را مشاهده کنید.

 
[TestCase(-1)]
[TestCase(0)]
[TestCase(1)]
public void ReturnFalseGivenValuesLessThan2(int value)
{
    var result = _primeService.IsPrime(value);

    Assert.IsFalse(result, $"{value} should not be prime");
}

این data driven test سه مقدار یعنی 0 و 1 و -1 را بر روی متد IsPrime تست می کند. به [TestCase] دقت کنید. با استفاده از دستور dotnet test اقدام به run کردن این test ها کنید. خواهید دید که دو مورد از این test ها fail می شوند. برای اینکه هر سه test نوشته شده pass شوند کافی دستور if ای که در ابتدای متد IsPrime آمده است را با دستور زیر جایگزین کنید.