تست مداوم (continuous testing) درNET.

قابلیت Live Unit Testing در ویژوال استودیو 2017 موجب توجه بیشتر به عملکرد تست مداوم (continuous testing) شده است. این مقاله توضیح می‌دهد که چگونه تست مداوم کار می‌کند، چرا باید به آن توجه کنید و چگونه می‌توانید به عنوان یک توسعه‌دهنده، از آن در NET. استفاده کنید.

تست مداوم (continuous testing) درNET.

تست مداوم چیست؟

یک عامل مهم در unit testing مؤثر این است که چقدر زمان صرف می‌کند تا توسعه‌دهنده نتایج تست را ببیند.

زمان کوتاه‌تر، تست سودمندی را ارائه می‌دهد. اگر تست‌ها به عنوان بخشی از آنچه که هر شب ساخته می‌شوند اجرا شوند، اطلاعات مربوط به هر کدام از آن‌ها که شکست خورده است، فقط صبح فردا در دسترس خواهد بود. تا آن زمان، توسعه‌دهنده به احتمال زیاد برخی از جزئیات راجع به تغییراتی که روز قبل انجام شده است را فراموش خواهد کرد، به ویژه اگر در عین حال، او شروع به کار روی موضوع دیگری کند.

ساخت پروژه، اجرای بلافاصله تست‌ها روی سرور بعد از تغییرات کد و کامیت برای کد منبع، عمدتا از این مشکل اجتناب می‌کند، زیرا نتایج فقط در مدت زمان ساخت به تأخیر می‌افتند.

البته توسعه‌دهنده می‌تواند تست‌ها را، به صورت لوکال (محلی) در محیط توسعه خود قبل از کامیت کد، خودش اجرا کند. این کار می‌تواند در وهله اول، در زمان صرفه‌جویی کرده و مانع کامیت شدن کدهای شکست‌خورده شود. اما حتی در این مورد، او باید آگاهانه تست‌ها را به ترتیب اجرا کند تا نتایج را ببیند، که معمولا زمانی این کار را انجام می‌دهد که بخشی از کار تکمیل شده باشد.

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

تست‌ها باید هر زمان که کد تغییر می‌کند، به صورت خودکار اجرا شوند و لازم نباشد توسعه‌دهنده آن‌ها را به صورت دستی اجرا کند. این امر می‌تواند مفید باشد، حتی اگر توسعه‌دهنده، بعد از نوشتن کدهای تحت تست، فقط تست‌ها را بنویسد. هنگامی که او نیاز به اصلاح یا افزایش کدهای موجود دارد، این تست‌ها بنا بر وظایف تست‌های رگرسیون (regression test) گرفته خواهند شد، یعنی آن‌ها به عنوان یک شبکه ایمنی از قابلیت‌های اصلی شکست خوردن، به کار گرفته می‌شوند.

با این حال، تست مداوم ارزش واقعی خود را زمانی نشان می‌دهد که تست‌ها در حین پیش‌روی کار یا موازی با کدهای تحت تست نوشته می‌شوند، همانطور که توسط Test Driven Development (TDD) مورد نیاز است.

وقتی توسعه‌دهنده در حال نوشتن کد است، تست‌ها در حال اجرا هستند و نتایج تست، نمایش آپدیت‌شده‌ای را در وضعیت جاری کد ارائه می‌دهد. این امر باعث می‌شود تا توسعه‌دهنده در هر اصلاحی که در کد انجام می‌دهد، خشنود بوده و به طور مؤثرتری عمل کند، چرا که نتایج تست بلافاصله تغییر می‌کند.

پشتیبانی تست مداوم در ابزارهای توسعه

اگر ابزارهای توسعه از تست مداوم پشتیبانی نکنند، کار با آن امکان‌پذیر نیست.

تا آنجا که ما می‌دانیم، اولین ابزار برای تست مداوم در اکوسیستم .NET، Mighty Moose بود، همچنین به عنوان ContinuousTests شناخته شد (افزونه‌ای در ویژوال استودیو و اجراکننده خط فرمان، که در ابتدا ابزار تجاری بود اما بعدها به صورت رایگان و open source عرضه شد). اما به نظر می‌رسد که امروزه به طور کامل منسوخ شده است.

این اولین تلاش بود که طولی نکشید توسط افزونه‌های تجاری شخص ثالث (party-third) برای ویژوال استودیو دنبال شد. امروزه سه راه‌حل رقابتی موجود است:

NCrunch از Remco Software

DotCover توسط JetBrains، به عنوان بخشی از بسته‌های نهایی ReSharper فروخته شده است

Smart Runner توسط Typemock، همراه با Typemock Isolator برای .NET فروخته شده است

اولین تلاش آزمایشی مایکروسافت در این زمینه، ویژگیی بود که به Test Explorer اضافه شد: اجرای تست‌ها بعد از ساخت، امکان اجرای خودکار تست‌ها بعد از هر ساخت را ایجاد کرد.

در حالی که این ویژگی برای تست‌های مداوم واقعی اجازه داده نمی‌شد، اما اولین قدم در این راستا بود. یک راه‌حل کامل برای تست مداوم در ویژوال استودیو 2017 به نام Live Unit Testing معرفی شد.

تقریبا در همان زمان، ویژگی مشابه‌ای به ابزار خط فرمان برای NET Core. اضافه شده بود. می‌توانید از فایل dotnet-watch برای نظارت کد منبع جهت تغییرات و اجرای تست‌ها استفاده کنید. این با ویژوال استودیو ادغام نمی‌شود و نتایج فقط در پنجره کنسول گزارش می‌شوند، اما می‌تواند با هر ویرایشگر کد و هر پلت‌فرمی استفاده شود.

در ادامه این مقاله، نگاه دقیق‌تری به راه‌حل‌های فعلی مایکروسافت برای تست مداوم می‌اندازیم.

Live Unit Testing در ویژوال استودیو 2017

قابلیت Live Unit Testing فقط در ویرایش Enterprise ویژوال استودیو 2017 موجود است.

در انتشار نهایی ویژوال استودیو 2017، که در ماه مارس منتشر شد، پشتیبانی تنها محدود به پروژه‌هایی بود که فریم‌ورک .NET را هدف قرار داده بودند. اگر می‌خواهید از آن در NET Core. (1.0, 1.1 یا preview 2.0) استفاده کنید، باید آپدیت 15.3 را نصب کنید. در زمان نوشتن، فقط به عنوان پیش‌نمایش در دسترس بود، که می‌توانست در کنار آن با ورژن منتشرشده موجود در ویژوال استودیو 2017 نصب شود.

شما می‌توانید از هر یک از فریم‌ورک‌های محبوب تست (MSTest، NUnit و xUnit.net) با Live Unit Testing استفاده کنید. با این حال برای همه آن‌ها باید از ورژن نسبتا جدیدی استفاده کنید، مثلا هیچ پشتیبانی برای NUnit 2 و MSTest v1 وجود ندارد.

راه‌اندازی پروژه ASP.NET Core

پروژه جدیدی بر اساس قالب پروژه (NET Core.) ASP.NET Core Web Application ایجاد می‌کنیم. گزینه Web Application را با No Authentication و Enable Docker Support انتخاب می‌کنیم.

با این تنظیمات، ویژوال استودیو یک برنامه را درون یک Linux container بر روی دستگاه ویندوز شما اجرا می‌کند. البته به Docker برای ویندوزها نیاز دارد تا نصب شود.

برای تست‌ها نیاز داریم پروژه دیگری را در سولوشن خودمان اضافه کنیم.

همان‌طور که می‌خواهیم از فریم‌ورک تست MSTest استفاده کنیم، قالب پروژه (Unit Test Project (.NET Core را انتخاب خواهیم کرد. برای تست‌های xUnit.net باید قالب پروژه نصب شده (xUnit Test Project (.NET Core را انتخاب کنیم.

هر دو قالب پروژه سعی می‌کند فریم‌ورک تست و پکیج‌های test adapter Nuget برای انتخاب فریم‌ورک تست را نصب کند، که برای ویژوال استودیو مورد نیاز است تا تست‌ها را تشخیص داده و آن‌ها را اجرا کند.

برای بررسی اینکه همه چیز به درستی تنظیم شده است،حالا می‌توانیم همه تست‌ها را از Test Explorer اجرا کنیم.
ابتدا باید تست خالی را در پروژه تستی که ساختیم پیدا کرده و آن را اجرا کند. اگرچه برنامه برای اجرا در Docker تنظیم شده است، روی پروژه تست تأثیری نمی‌گذارد. ویژوال استودیو باز هم تست‌ها را به صورت لوکال در قسمت اختصاصی خود اجرا خواهد کرد.

حالا زمان فعال کردن Live Unit Testing برای سولوشن‌مان از طریق فرمان Start در منوی Test > Live Unit Testing رسیده است. برای اینکه بعدا دوباره آن را غیرفعال کنیم، می‌توانیم از فرمان Pause یا Stop از همان منو استفاده کنیم.

همچنین لازم به ذکر است که هر بار که ویژوال استودیو را ریست می‌کنید، به طور پیش‌فرض باید Live Unit Testing را دوباره راه‌اندازی کنید. می‌توانید این رفتار را با تنظیماتی در صفحه Live Unit Testing در گزینه‌: Start Live Unit testing on solution load تغییر دهید.

چرخه توسعه

Live Unit Testing وقتی که ما عملکرد Test Driven Development را انجام می‌دهیم و همزمان که کد تحت تست را می‌نویسیم، تست‌ها را هم می‌نویسیم، بهتر خودش را نشان می‌دهد.

بیایید با افزودن یک servise class به پروژه ASP.NET Core خودمان شروع کنیم:

using System;
 
namespace LUTsample.Services
{
    public class FibonacciService
    {
        public int Calculate(int n)
        {
            throw new NotImplementedException();
        }
    }
}

تابع Calculatte سرانجام n تا عدد از دنباله Fibonacci را باز خواهد گرداند. تا زمانی که آن را اجرا نکرده‌ایم، به نظر می‌رسد که بهتر است که یک NotImplementedException را بگرداند. قبل از اجرای آن، باید اول تست آن را بنویسیم، و نتیجه‌ای که از آن انتظار داریم را مشخص کنیم:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using LUTsample.Services;
 
namespace LUTsample.Tests
{
    [TestClass]
    public class FibonacciTest
    {
        [TestMethod]
        public void Calculate1()
        {
            var fibonacci = new FibonacciService();
            Assert.AreEqual(1, fibonacci.Calculate(1));
        }
    }
}

البته که تست شکست می‌خورد.

این به وضوح در پنجره ویرایشگر کد توسط Live Unit testing، همین که تست را بنویسیم، نشان داده می‌شود.

علامت‌هایی که در جلوی هر خط کد وجود دارد، روش مؤثر نمایش اطلاعات در مورد نتایج تست و پوشش برنامه است:

تیک سبز نشان می‌دهد که آن خط با حداقل یک تست اجرا شده است و این که تمام تست‌ها موفق شده‌اند.

X قرمز نشان می‌دهد که آن خط با حداقل یک تست اجرا شده است و اینکه حداقل یکی از این تست‌ها ناموفق بوده و شکست خورده است.

خط تیره آبی رنگ نشان می‌دهد که این خط با هیچ تستی اجرا نشده است.

وقت آن است که تست موفق‌شده را با پیاده‌سازی تابع Calculate بسازیم:

public int Calculate(int n)
{
    var fibonacci = new int[n + 1];
    fibonacci[0] = 0;
    fibonacci[1] = 1;
 
    for (int i = 2; i <= n; i++)
    {
        fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2];
    }
 
    return fibonacci[n];
}

نه تنها تست موفق اکنون انجام می‌شود، بلکه اطلاعات پوشش داده شده در پنجره برنامه نشان داده می‌شود.

با یک خط کد که توسط تست ما اجرا نمی‌شود، ما واقعا نیاز داریم تا تست‌های بیشتری را اضافه کنیم:

public void CalculateOutOfRange()
{
    var fibonacci = new FibonacciService();
    Assert.ThrowsException<ArgumentOutOfRangeException>(
        () => fibonacci.Calculate(-1));
}
 
[TestMethod]
public void Calculate1()
{
    var fibonacci = new FibonacciService();
    Assert.AreEqual(1, fibonacci.Calculate(1));
}
 
[TestMethod]
public void Calculate8()
{
    var fibonacci = new FibonacciService();
    Assert.AreEqual(21, fibonacci.Calculate(8));
}

همه خطوط برنامه حالا توسط تست‌ها پوشش داده شده‌اند. با این حال، یکی از تست‌ها شکست خورده است:

اگر روی هر علامت کلیک کنیم، یک پنچره pop-up همه تست‌های پوشش داده شده برای آن خط را لیست می‌کند. اگر روی تست شکست خورده در این لیست بایستیم، اطلاعات بیشتری در مورد شکست آن را نشان می‌دهد.

با دابل کلیک کردن روی تستی در لیست، به آن تست هدایت خواهیم شد.

گام منطقی بعدی، تثبیت کردن کد تابع است، به طوری که همه تست‌ها موفق خواهند شد، اما این مرحله را به عنوان تمرینی برای شما می‌گذاریم.

حالا شما باید تصور بسیار خوبی از نحوه چرخه توسعه وقتی TDD با تست مداوم عمل می‌کند، داشته باشید. هنگامی که شما از آن استفاده می‌کنید، این رویکرد باید کیفیت برنامه شما را بهبود بخشد و برای شما کارآمدتر باشد.

ابزارهای خط فرمان (Command Line) .NET Core

NET Core SDK. شامل مجموعه‌ای از ابزارهای خط فرمان است که می‌تواند به عنوان یکی دیگر از رابط‌های کاربری گرافیکی ویژوال استودیو استفاده شود. ما از آن برای آماده‌سازی محیط کار مشابه با تست مداوم استفاده می‌کنیم.

راه‌اندازی پروژه

بیایید با ایجاد یک سولوشن جدید با یک پروژه پیش‌فرض ASP.NET Core MVC web application در داخل آن شروع کنیم:

md LUTsample
cd .\LUTsample\
dotnet new sln -n LUTsample
dotnet new mvc -n LUTsample -o LUTsample
dotnet sln add .\LUTsample\LUTsample.csproj

همانند قالب پروژه در ویژوال استودیو، قالب mvc هم یک برنامه وب را ایجاد می‌کند. متأسفانه، هیچ قالب دیگری جهت راه‌اندازی برای Docker container از خط فرمان وجود ندارد، بنابراین این برنامه به صورت لوکال اجرا خواهد شد. فقط باید پکیج‌های NuGet را بازیابی کنیم و آماده رفتن شویم:

dotnet restore
cd .\LUTsample\
dotnet run

می‌توانیم صفحه وب را در http://localhost:5000 در مرورگر دلخواه‌مان باز کنیم، همان طور که در خروجی آخرین دستور بیان شده است:

Hosting environment: Production
Content root path: D:\Users\Damir\Documents\LUTsample\LUTsample
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.

وقت آن است که یک پروژه تست همراه با آن ایجاد کرده و آن را به سولوشن اضافه کنیم. اجازه دهید وب سرور لوکال را با Ctrl+C متوقف کرده و دستورات زیر را اجرا کنیم:

cd..
dotnet new mstest -n LUTsample.Tests -o LUTsample.Tests
cd .\LUTsample.Tests\

mstest یک پروژه تست جدید را بر اساس فریم‌ورک تست MSTest v2 با یک نمونه تست خالی ایجاد خواهد کرد. xunit هم در xUnit.net نصب شده استفاده خواهد شد. پس از بازیابی پکیج‌های NuGet می‌توانیم تست‌ها را اجرا کنیم:

dotnet restore
dotnet test

البته که تست خالی موفق خواهد شد.

Build started, please wait...
Build completed.
 
Test run for D:\Users\Damir\Documents\LUTsample\LUTsample.Tests\bin\Debug\netcoreapp1.1\LUTsample.Tests.dll(.NETCoreApp,Version=v1.1)
Microsoft (R) Test Execution Command Line Tool Version 15.0.0.0
Copyright (c) Microsoft Corporation.  All rights reserved.
 
Starting test execution, please wait...
 
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.
Test execution time: 0.9738 Seconds

اجرای تست به صورت مداوم

حالا باید تغییر و اصلاح کد را شروع کنیم.

از آنجا که ابزارهای خط فرمان از همه عملیات خاص NET Core. پشتیبانی می‌کنند، می‌توانیم از هر ویرایشگر برنامه‌ای استفاده کنیم. ما ویژوال استودیو را انتخاب می‌کنیم، که برای ویندوزها، لینوکس و macOS، مانند ASP.NET Core خودش در دسترس است.

با نصب افزونه  #C، همین که فولدر سولوشن را در ویرایشگر باز کنیم، ویژوال استودیو پیشنهاد می‌دهد که به صورت خودکار launchهای مختلف و موارد لازم را برای سولوشن ما تولید کند.

این عمل باعث ایجاد فایل‌های launch.json و  tasks.json در زیر پوشه vscode. می‌شود، و به ما اجازه می‌دهد تا به سرعت پروژه را با Ctrl+Shift+B بسازیم و با F5 آن را دیباگ کنیم.

ما می‌خواهیم روندی مشابه را با ویژوال استودیو 2017 دنبال کنیم.

ابتدا FibonacciService را با تابع پیاده‌سازی نشده Calculate به فولدر جدید Services درون فولدر پروژه LUTsample اضافه می‌کنیم. سپس، تست خالی را در پروژه تست LUTsample با یک مقدار واقعی جایگزین می‌کنیم.

برای آنکه پروژه تست با موفقیت انجام شود، باید رفرنسی را به برنامه وب اضافه کنیم:

dotnet add reference ..\LUTsample\LUTsample.csproj

حالا می‌توانیم این تست شکست‌خورده جدید را با تست dotnet اجرا کنیم، اما از آنجایی که می‌خواهیم تست‌ها را بصورت مداوم اجرا کنیم، باید ابزار dotnet-watch را به پروژه تست اضافه کنیم، می‌توانید  دستورالعمل‌ها را در GitHub دنبال کنید.

فایل LUTsample.Tests.csproj را در ویرایشگر باز کنید و دستور زیر را درون عنصر Project آن اضافه کنید:

<itemgroup>   <dotnetclitoolreference include="Microsoft.DotNet.Watcher.Tools" version="1.0.1"></dotnetclitoolreference> </itemgroup>

این عمل با NET Core 1.0. و NET Core 1.1. کار خواهد کرد. برای پروژه‌های NET Core 2.0. باید از ورژن 2.0.0 در Microsoft.Dotnet.Watcher.Tools استفاده کنیم.

پس از بازیابی پکیج‌های جدید NuGet، می‌توانیم ابزار watch را برای اجرای تست بررسی کنیم:

dotnet restore
dotnet watch test

این موارد، جزئیات در مورد تست شکست‌خورده را ایجاد خواهد کرد، اما برخلاف تست dotnet، اجرا را ادامه خواهد داد و بر هرگونه تغییری در برنامه یا پروژه تست نظارت دارد:

watch : Started
Build started, please wait...
Build completed.
 
Test run for D:\Users\Damir\Temp\LUTsample\LUTsample.Tests\bin\Debug\netcoreapp1.1\LUTsample.Tests.dll(.NETCoreApp,Version=v1.1)
Microsoft (R) Test Execution Command Line Tool Version 15.0.0.0
Copyright (c) Microsoft Corporation.  All rights reserved.
 
Starting test execution, please wait...
Failed   LUTsample.Tests.FibonacciTest.Calculate1
Error Message:
 Test method LUTsample.Tests.FibonacciTest.Calculate1 threw exception:
System.NotImplementedException: The method or operation is not implemented.
Stack Trace:
    at LUTsample.Services.FibonacciService.Calculate(Int32 n) in D:\Users\Damir\Temp\LUTsample\LUTsample\Services\FibonacciService.cs:line 9
   at LUTsample.Tests.FibonacciTest.Calculate1() in D:\Users\Damir\Temp\LUTsample\LUTsample.Tests\FibonacciTest.cs:line 13
 
 
Total tests: 1. Passed: 0. Failed: 1. Skipped: 0.
Test Run Failed.
Test execution time: 0.8896 Seconds
 
 
watch : Exited with error code 1
watch : Waiting for a file to change before restarting dotnet...

به محض اینکه پیاده‌سازی را برای متد FibonacciService.Calculate اضافه کنیم و تغییرات را ذخیره کنیم، ابزار watch پروژه را بازسازی (rebuild) کرده و مجددا تست‌ها را اجرا می‌کند:

watch : Started
Build started, please wait...
Build completed.
 
Test run for D:\Users\Damir\Temp\LUTsample\LUTsample.Tests\bin\Debug\netcoreapp1.1\LUTsample.Tests.dll(.NETCoreApp,Version=v1.1)
Microsoft (R) Test Execution Command Line Tool Version 15.0.0.0
Copyright (c) Microsoft Corporation.  All rights reserved.
 
Starting test execution, please wait...
 
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.
Test execution time: 0.8763 Seconds
 
 
watch : Exited
watch : Waiting for a file to change before restarting dotnet...

برخلاف  Live Unit Testing، نتایج و پوشش‌ها به طور مستقیم در ویرایشگر برنامه نشان داده نمی‌شوند، اما ما هنوز بازخورد فوری را با هر تغییر دریافت می‌کنیم. اگر اکنون تست‌های بیشتری را اضافه کنیم، آن‌ها دوباره بلافاصله اجرا می‌شوند، که نشان دهد یکی از آن‌ها شکست خورده است.

برای راحتی بیشتر، شما می‌توانید حتی dotnet-watch را بصورت مستقیم در ترمینالی که در ویژوال استودیو ساخته شده است، اجرا کنید. به این ترتیب نیازی به داشتن پنجره ترمینال اضافی باز در هر زمان ندارید.

نتیجه‌گیری

ما بررسی کردیم که چگونه ابزارهای تست مداوم می‌توانند بخش کاملی از فرآیند توسعه شما را، خواه از TDD استفاده کنید یا نه، تست کنند.

پشتیبانی از تست مداوم در اکوسیستم .NET بسیار رایج شده است و در آینده بهبود خواهد یافت. ابزارهای خط فرمان NET Core. می‌توانند با هر ویرایشگر و روی هر پلت‌فرمی استفاده شوند. ویژوال استودیو 2017 ویژگی Live Unit Testing را معرفی می‌کند، که فقط در نسخه Enterprise موجود است. برای نسخه‌های دیگر، افزونه‌های تجاری third-party دیگری موجود است.

دانلود نسخه ی PDF این مطلب