مطالب
آشنایی با mocking frameworks - قسمت دوم

استفاده از mocking frameworks :

تعدادی از چارچوب‌های تقلید نوشته شده برای دات نت فریم ورک مطابق لیست زیر بوده و هدف از آن‌ها ایجاد ساده‌تر اشیاء تقلید برای ما می‌باشد:

Nmock : http://www.nmock.org
Moq : http://code.google.com/p/moq
Rhino Mocks : http://ayende.com/projects/rhino-mocks.aspx
TypeMock : http://www.typemock.com
EasyMock.Net : http://sourceforge.net/projects/easymocknet

در این بین Rhino Mocks که توسط یکی از اعضای اصلی تیم NHibernate به وجود آمده است، در مجامع مرتبط بیشتر مورد توجه است. برای آشنایی بیشتر با آن می‌توان به این ویدیوی رایگان آموزشی در مورد آن مراجعه نمود (حدود یک ساعت است).



خلاصه‌ا‌ی در مورد نحوه‌ی استفاده از Rhino Mocks :
پس از دریافت کتابخانه سورس باز Rhino Mocks ، ارجاعی را به اسمبلی Rhino.Mocks.dll آن، در پروژه آزمون واحد خود اضافه نمائید.
یک Rhino mock test با ایجاد شیءایی از MockRepository شروع می‌شود و کلا از سه قسمت تشکیل می‌گردد:
الف) ایجاد شیء Mock یا Arrange . هدف از ایجاد شیء mock ، جایگزین کردن و یا تقلید یک شیء واقعی جهت مباحثی مانند ایزوله سازی آزمایشات، بالابردن سرعت آن‌ها و متکی به خود کردن این آزمایشات می‌باشد. همچنین در این حالت نتایج false positive نیز کاهش می‌یابند. منظور از نتایج false positive این است که آزمایش باید با موفقیت به پایان برسد اما اینگونه نشده و علت آن بررسی سیستمی دیگر در خارج از مرزهای سیستم فعلی است و مشکل از جای دیگری نشات گرفته که اساسا هدف از تست ما بررسی عملکرد آن سیستم نبوده است. کلا در این موارد از mocking objects استفاده می‌شود:
- دسترسی به شیء مورد نظر کند است مانند دسترسی به دیتابیس یا محاسبات بسیار طولانی
- شیء مورد نظر از call back استفاده می‌کند
- شیء مورد آزمایش باید به منابع خارجی دسترسی پیدا کند که اکنون مهیا نیستند. برای مثال دسترسی به شبکه.
- شیءایی که می‌خواهیم آن‌را تست کنیم یا برای آن آزمایشات واحد تهیه نمائیم، هنوز کاملا توسعه نیافته و نیمه کاره است.
ب) تعریف رفتارهای مورد نظر یا Act
ج) بررسی رفتارهای تعریف شده یا Assert

مثال:
متد ساده زیر را در نظر بگیرید:

public class ImageManagement
{
public string GetImageForTimeOfDay()
{
int currentHour = DateTime.Now.Hour;
return currentHour > 6 && currentHour < 21 ? "sun.jpg" : "moon.jpg";
}

}
آزمایش این متد، وابسته است به زمان جاری سیستم.

using System;
using NUnit.Framework;

[TestFixture]
public class CMyTest
{
[Test]
public void DaytimeTest()
{
int currentHour = DateTime.Now.Hour;

if (currentHour > 6 && currentHour < 21)
{
const string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay();
Assert.AreEqual(expectedImagePath, path);
}
else
{
Assert.Ignore("تنها در طول روز قابل بررسی است");
}
}

[Test]
public void NighttimeTest()
{
int currentHour = DateTime.Now.Hour;

if (currentHour < 6 || currentHour > 21)
{
const string expectedImagePath = "moon.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay();
Assert.AreEqual(expectedImagePath, path);
}
else
{
Assert.Ignore("تنها در طول شب قابل بررسی است");
}
}

}
برای مثال اگر بخواهیم تصویر ماه را دریافت کنیم باید تا ساعت 21 صبر کرد. همچنین بررسی اینکه چرا یکی از متدهای آزمون واحد ما نیز با شکست مواجه شده است نیز نیازمند بررسی زمان جاری است و گاهی ممکن است با شکست مواجه شود و گاهی خیر. در این‌جا با استفاده از یک mock object ، این وضعیت غیرقابل پیش بینی را با منطقی از پیش طراحی شده جایگزین کرده و آزمون خود را بر اساس آن انجام خواهیم داد.
برای این‌کار باید DateTime.Now.Hour را تقلید نموده و اینترفیسی را بر اساس آن طراحی نمائیم. سپس Rhino Mocks کار پیاده سازی این اینترفیس را انجام خواهد داد:

using NUnit.Framework;
using Rhino.Mocks;

namespace testWinForms87
{
public interface IDateTime
{
int GetHour();
}

public class ImageManagement
{
public string GetImageForTimeOfDay(IDateTime time)
{
int currentHour = time.GetHour();

return currentHour > 6 && currentHour < 21 ? "sun.jpg" : "moon.jpg";
}
}

[TestFixture]
public class CMocking
{
[Test]
public void DaytimeTest()
{
MockRepository mocks = new MockRepository();
IDateTime timeController = mocks.CreateMock<IDateTime>();

using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(15);
}

using (mocks.Playback())
{
const string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
}

[Test]
public void NighttimeTest()
{
MockRepository mocks = new MockRepository();
IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(1);
}

using (mocks.Playback())
{
const string expectedImagePath = "moon.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
}
}

}
همانطور که در ابتدای مطلب هم عنوان شد، mocking‌ از سه قسمت تشکیل می‌شود:

MockRepository mocks = new MockRepository();
ابتدا شیء mocks را از MockRepository کتابخانه Rhino Mocks ایجاد می‌کنیم تا بتوان از خواص و متدهای آن استفاده کرد.
سپس اینترفیسی باید به آن پاس شود تا انتظارات سیستم را بتوان در آن بر پا نمود:

IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(15);

}
به عبارت دیگر در اینجا به سیستم مقلد خود خواهیم گفت: زمانیکه شیء ساعت را تقلید کردی، لطفا عدد 15 را برگردان.
به این صورت آزمایش ما بر اساس وضعیت مشخصی از سیستم صورت می‌گیرد و وابسته به ساعت جاری سیستم نخواهد بود.

همانطور که ملاحظه می‌کنید، روش Test Driven Development بر روی نحوه‌ی برنامه نویسی ما و ایجاد کلاس‌ها و اینترفیس‌های اولیه نیز تاثیر زیادی خواهد گذاشت. استفاده از اینترفیس‌ها یکی از اصول پایه‌ای برنامه نویسی شیءگرا است و در اینجا مقید به ایجاد آن‌ها خواهیم شد.

پس از آن‌که در قسمت mocks.Record ، انتظارات خود را ثبت کردیم، اکنون نوبت به وضعیت Playback می‌رسد:
using (mocks.Playback())
{
string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);

}
در اینجا روش کار همانند ایجاد متدهای آزمون واحد متداولی است که تاکنون با آن‌ها آشنا شده‌ایم و تفاوتی ندارد.
با توجه به اینکه پس از تغییر طراحی متد GetImageForTimeOfDay ، این متد اکنون از شیء IDateTime به عنوان ورودی استفاده می‌کند، می‌توان پیاده سازی آن اینترفیس ‌را در آزمایشات واحد تقلید نمود و یا جایی که قرار است در برنامه استفاده شود، می‌تواند پیاده سازی واقعی خود را داشته باشد و دیگر آزمایشات ما وابسته به آن نخواهد بود:

public class DateTimeController : IDateTime
{
public int GetHour()
{
return DateTime.Now.Hour;
}
}

مطالب
مروری بر کاربردهای Action و Func - قسمت دوم
در قسمت قبل از  Func و Actionها برای ساده سازی طراحی‌های مبتنی بر اینترفیس‌هایی با یک متد استفاده کردیم. این مورد خصوصا در حالت‌هایی که قصد داریم به کاربر اجازه‌ی فرمول نویسی بر روی اطلاعات موجود را بدهیم، بسیار مفید است.

مثال دوم) به استفاده کننده از API کتابخانه خود، اجازه فرمول نویسی بدهید

برای نمونه مثال ساده زیر را درنظر بگیرید که در آن قرار است یک سری عدد که از منبع داده‌ای دریافت شده‌اند، بر روی صفحه نمایش داده شوند:
public static void PrintNumbers()
{
    var numbers = new[] { 1,2,3,5,7,90 }; // from a data source
    foreach(var item in numbers)
    {
        Console.WriteLine(item);
    }    
}
قصد داریم به برنامه نویس استفاده کننده از کتابخانه گزارش‌سازی خود، این اجازه را بدهیم که پیش از نمایش نهایی اطلاعات، بتواند توسط فرمولی که مشخص می‌کند، فرمت اعداد نمایش داده شده را تعیین کند.
روال کار اکثر ابزارهای گزارش‌سازی موجود، ارائه یک زبان اسکریپتی جدید برای حل این نوع مسایل است. اما با استفاده از Func و ... روش‌های Code first (بجای روش‌های Wizard first)، خیلی از این رنج و دردها را می‌توان ساده‌تر و بدون نیاز به اختراع و یا آموزش زبان جدیدی حل کرد:
public static void PrintNumbers(Func<int,string> formula)
{
    var numbers = new[] { 1,2,3,5,7,90 };  // from a data source
    foreach(var item in numbers)
    {
        var data = formula(item);
        Console.WriteLine(data);
    }    
}
اینبار با استفاده از Func، امکان فرمول نویسی را به کاربر استفاده کننده از API ساده گزارش ساز فرضی خود داده‌ایم. Func تعریف شده در اینجا یک عدد int را در اختیار استفاده کننده قرار می‌دهد. در این بین، برنامه نویس می‌تواند هر نوع تغییر یا هر نوع فرمولی را که مایل است بر روی این عدد به کمک دستور زبان جاری مورد استفاده، اعمال کند و در آخر تنها باید نتیجه این عملیات را به صورت یک string بازگشت دهد. برای مثال:
 PrintNumbers(number => string.Format("{0:n0}",number));
البته سطر فوق ساده شده فراخوانی زیر است:
 PrintNumbers((number) =>{ return string.Format("{0:n0}",number); });
به این ترتیب اعداد نهایی با جدا کننده سه رقمی نمایش داده خواهند شد.
از این نوع طراحی، در ابزارها و کتابخانه‌های جدید گزارش سازی مخصوص ASP.NET MVC زیاد مشاهده می‌شوند.


مثال سوم) حذف کدهای تکراری برنامه

فرض کنید قصد دارید در برنامه وب خود مباحث caching را پیاده سازی کنید:
using System;
using System.Web;
using System.Web.Caching;
using System.Collections.Generic;

namespace WebToolkit
{
    public static class CacheManager
    {
        public static void CacheInsert(this HttpContextBase httpContext, string key, object data, int durationMinutes)
        {
            if (data == null) return;
            httpContext.Cache.Add(
                key,
                data,
                null,
                DateTime.Now.AddMinutes(durationMinutes),
                TimeSpan.Zero,
                CacheItemPriority.AboveNormal,
                null);
        }
    }
}
در هر قسمتی از برنامه که قصد داشته باشیم اطلاعاتی را در کش ذخیره کنیم، الگوی تکراری زیر باید طی شود:
var item = httpContext.Cache[key];
if (item == null)
{
    item = ReadDataFromDataSource();
    if (item == null)
          return null;

    CacheInsert(httpContext, key, item, durationMinutes);
}
ابتدا باید وضعیت کش جاری بررسی شود؛ اگر اطلاعاتی در آن موجود نبود، ابتدا از منبع داده‌ای مورد نظر خوانده شده و سپس در کش درج شود.
می‌توان در این الگوی تکراری، خواندن اطلاعات را از منبع داده، به یک Func واگذار کرد و به این صورت کدهای ما به نحو زیر بازسازی خواهند شد:
using System;
using System.Web;
using System.Web.Caching;
using System.Collections.Generic;

namespace WebToolkit
{
    public static class CacheManager
    {
        public static void CacheInsert(this HttpContextBase httpContext, string key, object data, int durationMinutes)
        {
            if (data == null) return;
            httpContext.Cache.Add(
                key,
                data,
                null,
                DateTime.Now.AddMinutes(durationMinutes),
                TimeSpan.Zero,
                CacheItemPriority.AboveNormal,
                null);
        }

        public static T CacheRead<T>(this HttpContextBase httpContext, string key, int durationMinutes, Func<T> ifNullRetrievalMethod)
        {
            var item = httpContext.Cache[key];
            if (item == null)
            {
                item = ifNullRetrievalMethod();
                if (item == null)
                    return default(T);

                CacheInsert(httpContext, key, item, durationMinutes);
            }
            return (T)item;
        }
    }
}
و استفاده از آن نیز به نحو زیر خواهد بود:
var user = HttpContext.CacheRead(
                            "Key1",
                            15,
                            () => _usersService.FindUser(userId));
پارامتر سوم متد CacheRead به صورت خودکار تنها زمانیکه اطلاعات کش متناظری با کلید Key1 وجود نداشته باشند، اجرا شده و نتیجه در کش ثبت می‌گردد. در اینجا دیگر از if و else و کدهای تکراری بررسی وضعیت کش خبری نیست.
 
مطالب
نصب و راه اندازی SQL Server بر روی لینوکس با استفاده از Docker
 چند وقتی است مایکروسافت تعدادی از محصولات خود را به صورت سورس باز در اختیار برنامه نویسان قرار داده‌است. برای مثال شما می‌توانید در لینوکس یا مک، از نسخه‌ی net core. آن استفاده کنید. در این مقاله روش اجرای sql server را در لینوکس، با استفاده از docker؛ تشریح خواهیم کرد. همچنین با یک پروژه ساده net core.، بر روی دیتابیس add-migration را اجرا کرده و همچنین چند رکورد را در جدولی ثبت می‌کنیم. البته می‌توان نسخه‌ی نصبی sql server را بدون نیاز به docker نیز دانلود و آن را بر روی لینوکس نصب کرد.  


در این مقاله چه چیزی را پوشش خواهیم داد:‌

· راه اندازی داکر
· پیکره‌بندی container image
· وصل شدن به sql
· ساخت یک پروژه ساده net core.
· ایجاد دیتابیس
· ثبت رکورد در دیتابیس

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


بعد از اینکه docker را بر روی سیستم خود نصب کردید، می‌توانید از دستورات داکر استفاده کنید. در این مقاله می‌خواهیم sql server را بر روی داکر نصب و راه اندازی کنیم.
 

دانلود و نصب sql server بر روی داکر

ابتدا وارد این لینک شوید. همانطور که مشاهده میکنید، SQL Server در 3 نسخه‌ی ویندوز، لینوکس و docker قابل دانلود می‌باشد. چون میخواهیم sql server را بر روی docker نصب کنیم، پس گزینه‌ی docker را انتخاب کنید.


قبل از هرچیز باید Image اس‌‌کیوال سرور را بر روی داکر دانلود نمائید. برای این کار وارد سایت dockerhub شوید و عبارت microsoft/mssql-server-linux را جستجو کنید.


همانطور که در تصویر نیز مشاهد می‌کنید، این بسته 10 میلیون بار دریافت شده‌است! در ادامه دستور زیر را در ترمینال خود Paste کنید و منتظر بمانید تا دانلود شود:
docker pull microsoft/mssql-server-linux:2017-latest
همچنین با اسکرول کردن در این صفحه می‌توانید آموزش نصب و راه ندازی این image را ببینید. بعد از دانلود image مخصوص داکر، با دستور docker images all می‌توانید images دانلود شده را مشاهده کنید. ولی image‌ها به خودی خود  کاربردی ندارند و باید آن‌ها را اجرا کنیم.
برای اجرای image sql از دستور زیر استفاده میکنیم:
 sudo docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=<YourStrong!Passw0rd>' \
-p 1433:1433 --name sql1 \
-d mcr.microsoft.com/mssql/server:2017-latest
در این دستور:
docker run –name sql : کار ساخت و اجرای Docker container ای به نام sql را انجام می‌دهد.
'e 'ACCEPT_EULA=Y- : سبب قرار دادن مقدار yes در ACCEPT_EULA که در قسمت environment variables تعریف شده‌است، می‌شود.
Set the  SA_PASSWORD : پسورد  environment variable ای که شما انتخاب می‌کنید.
p 1433:1433- : شماره پورتی که Docker container بر روی آن اجرا میشود.
-d microsoft/mssql-server-linux:2017-latest : نام Image ای که می‌خواهیم اجرا کنیم.

همانطور که مشاهده می‌کنید، Docker container بر روی پورت 1433 اجرا می‌شود. برای مشاهده جزئیات بیشتر، با وارد کردن دستو docker ps a می‌توان لیست containerها و وضعیت آن‌ها را مشاهده کرد.


همانطور که ملاحظه میکنید، در قسمت status، عبارت up به معنای در حال اجرا بودن container است. اگر عبارت دیگری را مشاهده کردید، با دستور dockr start id و وارد کردن شماره image خود می‌توانید آن را اجرا کنید.

تا اینجا توانستیم sql server  را اجرا کنیم. برای توضیحات بیشتر به این لینک مراجعه کنید.
 

وصل شدن به sql
برای وصل شدن به دیتابیس باید connection string دیتابیس مربوطه را داشته باشیم. با توجه به کانفیگ‌هایی که در بالا انجام دادیم، connection string ما به شکل زیر خواهد بود:
Server Host: localhost
Port: 1433
Authentication: SQL Server Authentication
Login: SA
Password: <StrongPasswordYouSet>
اگر کانکشن را به درستی کانفیگ کرده باشید، باید یک دیتابیس به نام انتخابی شما ایجاد شده باشد. در ادامه همین کار را بر روی یک پروژه‌ی  ساده netcore. انجام خواهیم داد. اما برای وصل شدن از طریق docker باید ابتدا bash (دستورات sqlcmd) را بارگذاری کنیم، تا بتوانیم به sqlcmd بر روی container در حال اجرا، دسترسی پیدا کنیم:
sudo docker exec -it sql1 "bash"
پس از آن باید sqlcmd  را به صورت مستقیم و از آدرس فیزیکی سیستم، درون container بارگذاری کنید:
/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P '<YourNewStrong!Passw0rd>'
اگر دستور فوق با موفقیت اجرا شود ، عبارت 1> در ترمینال به نمایش در می‌آید. یعنی هم اکنون می‌توانید با تایپ دستوارت، آن‌ها را در sqlcmd اجرا کنید:


تا اینجای کار sql server  آماده‌ی اجرا دستورات شما می‌باشد. در ادامه می‌خواهیم چند دستور ساده‌ی sql را بر روی آن اجرا کنیم.


ساخت دیتابیس
با دستور sqlcmd زیر، ابتدا یک دیتابیس را میسازیم:
 CREATE DATABASE TestDB

ساخت جدول
در ادامه، دستور زیر را برای ساخت جدول مینویسیم:
 CREATE TABLE Inventory (id INT, name NVARCHAR(50), quantity INT)

ایجاد رکورد
مرحله بعدی، ایجاد یک رکورد جدید در دیتابیس میباشد:
 INSERT INTO Inventory VALUES (1, 'banana', 150); INSERT INTO Inventory VALUES (2, 'orange', 154);

در آخر با استفاده از دستور go، کوئری‌های بالا را اجرا می‌کنیم. اکنون باید یک دیتابیس جدید به نام TestDB و یک جدول جدید نیز به نام Inventory همچنین یک رکورد جدید در آن ثبت شده باشد. برای مشاهده‌ی تغییرات بالا، از دستورات زیر استفاده میکنیم:
- با دستور زیر لیست دیتابیس‌های موجود را می‌توان دید:
 SELECT Name from sys.Databases
- کو ئری select از دیتابیس:
 SELECT * FROM Inventory WHERE quantity > 152;

و با استفاده از دستور quit میتوانید از cmd خارج شوید.

تا اینجا توانستیم docker را بر روی سیستم راه ندازی و همچنین sql server  را بر روی آن نصب و اجرا کنیم. همچنین با دستورات sqlcmd توانستیم بر روی sql کوئری بزنیم.


ساخت و وصل شدن یک پروژه‌ی net core. و وصل شدن به sql server

حال میخواهیم با یک پروژه‌ی ساده‌ی net core. به sql server فوق وصل شده و یک جدول را به دیتابیس مذکور اضافه کرده و یک کوئری اضافه کردن رکوردی را به آن جدول بنویسیم. برای شروع، یک پروژه‌ی خالی net core. را ایجاد می‌کنیم. برای مثال یک پروژه‌ی api را ایجاد میکنیم:
dotnet new webapi -o dockerapi
سپس دو پکیج زیر را به آن اضافه میکنیم:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
در این مثال می‌خواهیم جدول Students را ایجاد و یک رکورد را در آن ثبت نماییم. پس یک کلاس را به نام Students ساخته و property‌های زیر را در آن مینویسیم:
public class Students
{
       public int Id { get; set; }
       public string Name { get; set; }
       public string Phone { get; set; }
}
مرحله‌ی بعد، ساخت context میباشد. برای اینکه وارد جزئیات نشویم، از قابلیت Scaffold استفاده می‌کنیم و context را تولید میکنیم:
 dotnet ef dbcontext scaffold "Server=localhost,1433\\Catalog=tutorial_database;Database=<YOUR_DATABASE_NAME>;User=SA;Password=<StrongPasswordYouSet>;" Microsoft.EntityFrameworkCore.SqlServer
پس از اجرای دستور بالا، context ساخته میشود. حال دورن context، یک DbSet را از students ایجاد میکنیم. بعد نوبت به تنظیم کردن connection string می‌رسد. داخل کانتکست، connection string را تنظیم کنید. همچنین connection string داخل appsettings.json  را نیز تنظیم کنید:
"ConnectionStrings": {
  "TestingDatabase": "Server=localhost:1433\\Database=<YourDatabaseName>;User=SA;Password=<StrongPasswordYouSet>;"
}
بعد از تنظیم کردن connection string، باید migration را بزنیم تا تغییرات context را مشاهده کنیم. با دستور زیر migration خود را اضافه کنید:
 dotnet ef migrations add <NAME_OF_MIGRATION>


همانطور که مشاهده می‌کنید، migrations اضافه شده و موجودیت هم اضافه شده‌است. حال باید بر روی migrations خود آپدیت بزنیم:
ef database update

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

همانطور که مشاهده می‌کنید، دیتابیس برای ما ایجاد شده. با دستور زیر می‌توان جدول دیتابیس را مشاهده کرد:
 SELECT TABLE_NAME FROM dockerdb.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'