در قسمت مدل ابتدا یک کلاس پایه برای مدل ایجاد خواهیم کرد:
public abstract class Entity { public Guid Id { get; set; } }
public class Book : EntityBase { public string Name { get; set; } public decimal Author { get; set; } }
public class BookRepository { private readonly ConcurrentDictionary<Guid, Book> result = new ConcurrentDictionary<Guid, Book>(); public IQueryable<Book> GetAll() { return result.Values.AsQueryable(); } public Book Add(Book entity) { if (entity.Id == Guid.Empty) entity.Id = Guid.NewGuid(); if (result.ContainsKey(entity.Id)) return null; if (!result.TryAdd(entity.Id, entity)) return null; return entity; } }
نوبت به کلاس کنترلر میرسد. یک کنترلر Api به نام BooksController ایجاد کنید و سپس کدهای زیر را در آن کپی نمایید:
public class BooksController : ApiController { public static BookRepository repository = new BookRepository(); public BooksController() { repository.Add(new Book { Id=Guid.NewGuid(), Name="C#", Author="Masoud Pakdel" }); repository.Add(new Book { Id = Guid.NewGuid(), Name = "F#", Author = "Masoud Pakdel" }); repository.Add(new Book { Id = Guid.NewGuid(), Name = "TypeScript", Author = "Masoud Pakdel" }); } public IEnumerable<Book> Get() { return repository.GetAll().ToArray(); } }
در این کنترلر، اکشنی به نام Get داریم که در آن اطلاعات کتابها از Repository مربوطه برگشت داده خواهد شد. در سازنده این کنترلر ابتدا سه کتاب به صورت پیش فرض اضافه میشود و انتظار داریم که بعد از اجرای برنامه، لیست مورد نظر را مشاهده نماییم.
module Model { export class Book{ Id: string; Name: string; Author: string; } }
<div ng-controller="Books.Controller"> <table class="table table-striped table-hover" style="width: 500px;"> <thead> <tr> <th>Name</th> <th>Author</th> </tr> </thead> <tbody> <tr ng-repeat="book in books"> <td>{{book.Name}}</td> <td>{{book.Author}}</td> </tr> </tbody> </table> </div>
ابتدا یک کنترلری که به نام Controller که در ماژولی به نام Book تعریف شده است باید ایجاد شود. اطلاعات تمام کتب ثبت شده باید از سرویس مورد نظر دریافت و با یک ng-repeat در جدول نمایش داده خواهند شود.
در پوشه app یک فایل TypeScript دیگر برای تعریف برخی نیازمندیها به نام AngularModule ایجاد میکنیم که کد آن به صورت زیر خواهد بود:
declare module AngularModule { export interface HttpPromise { success(callback: Function) : HttpPromise; } export interface Http { get(url: string): HttpPromise; } }
در اینترفیس Http نیز تابعی به نام get تعریف شده است که برای دریافت اطلاعات از سرویس api، مورد استفاده قرار خواهد گرفت. از آن جا که تعریف توابع در اینترفیس فاقد بدنه است در نتیجه این جا فقط امضای توابع مشخص خواهد شد. پیاده سازی توابع به عهده کنترلرها خواهد بود:
مرحله بعد مربوط است به تعریف کنترلری به نام BookController تا اینترفیس بالا را پیاده سازی نماید. کدهای آن به صورت زیر خواهد بود:
/// <reference path='AngularModule.ts' /> /// <reference path='BookModel.ts' /> module Books { export interface Scope { books: Model.Book[]; } export class Controller { private httpService: any; constructor($scope: Scope, $http: any) { this.httpService = $http; this.getAllBooks(function (data) { $scope.books = data; }); var controller = this; } getAllBooks(successCallback: Function): void { this.httpService.get('/api/books').success(function (data, status) { successCallback(data); }); } } }
توضیح کدهای بالا:
برای دسترسی به تعاریف انجام شده در سایر ماژولها باید ارجاعی به فایل تعاریف ماژولهای مورد نظر داشته باشیم. در غیر این صورت هنگام استفاده از این ماژولها با خطای کامپایلری روبرو خواهیم شد. عملیات ارجاع به صورت زیر است:
/// <reference path='AngularModule.ts' /> /// <reference path='BookModel.ts' />
export interface Scope { books: Model.Book[]; }
در نهایت خروجی به صورت زیر خواهد بود:
سورس پیاده سازی مثال بالا در Visual Studio 2013
اصل چهارم: Starve for loosely coupled designs
"به دنبال طراحی با اتصال سست بین اجزا باش"
اتصال بین اجزای برنامه نویسی باعث سختتر شدن مدیریت تغییرات میشود؛ چرا که با تغییر یک بخش، بخشهای متصل نیز دچار مشکل خواهند شد. اتصالها از لحاظ نوع قدرت متفاوتند و اساسا سیستمی بدون اتصال وجود ندارد. لذا باید به دنبال یک طراحی با کمترین میزان قدرت اتصال یا همان سست اتصال باشیم.
تا به اینجا، اصلهای دوم و سوم ما را در کاهش وابستگی و اتصال قوی کمک کردهاند. استفاده از واسطها، باعث کاهش وابستگی به نوع پیاده سازی میشود. استفاده از ترکیب نیز به نوعی باعث از بین رفتن وابستگی قوی بین کلاسهای فرزند و کلاس والد میشود و با روشی دیگر (استفاده از شیء در برگرفته شده برای پیاده سازی وظیفهی تغییر کننده) وظایف را در کلاسها پیاده سازی میکند. در زیر نمونهی اتصال قوی و نتیجهی آن را میبینیم:
public class StrongCoupledConcreteA { public string GenerateString(string s) { return s + " from" + this.GetType().ToString(); } } public class StrongCoupledConcreteB { public void GenerateString(ref string s) { s += " from" + this.GetType().ToString(); } } public class Printer { bool condition; public Printer(bool cond) { condition = cond; } public void SetCondition(bool value) { condition = value; } public void Print() { string result; string input = " this message is"; if (condition) { var stringGenerator = new StrongCoupledConcreteA(); result = stringGenerator.GenerateString(input); } else { var stringGenerator = new StrongCoupledConcreteB(); result = input; stringGenerator.GenerateString(ref result); } Console.WriteLine(result); } } public class Context { Printer printer; public void DoWork() { printer = new Printer(true); printer.Print(); printer.SetCondition(false); printer.Print(); } }
حال کد بازنویسی شده را با آن مقایسه کنید:
public interface IStringGenerator { string GenerateString(string s); } public class LooslyCoupledConcreteA : IStringGenerator { public string GenerateString(string s) { return s + " from " + this.GetType().ToString(); } } public class LooslyCoupledConcreteB : IStringGenerator { public string GenerateString(string s) { return s + " from " + this.GetType().ToString(); } } public class Printer { bool condition; public Printer(bool cond) { condition = cond; } public void SetCondition(bool value) { condition = value; } public void Print() { string result; string input = " this message is"; IStringGenerator generator; if (condition) { generator = new LooslyCoupledConcreteA(); } else { generator = new LooslyCoupledConcreteB(); } result = generator.GenerateString(input); Console.WriteLine(result); } }
با کمی دقت مشاهده میکنیم که در کلاسهای strongly coupled با اینکه هدف هر دو کلاس تولید یک رشته است، ولی عدم وجود پروتکل باعث شده است نحوهی گرفتن ورودی و برگرداندن خروجی متفاوت شود و در نتیجه نیازمند به اضافه کردن پیچیدگی در کلاس فراخوانی کنندهی آنها میشویم. این در حالی است که در روش loosely coupled با ایجاد یک پروتکل (واسط IStringGenerator ) این پیچیدگی از بین رفته است. در اینجا نوع اتصال (وابستگی) از جنس اتصال (وابستگی) قوی به تعریف (prototype) و شاید به نوعی نحوهی پیاده سازی متد میباشد.
SOLID Principles *
پنج اصل بعدی به اصول SOLID معروف هستند.
S: Single Responsibility
O: Open/Closed
L: Liskov’s Substitution
I: Interface Segregation
D: Dependency Injection
اصل پنجم: Single responsibility
"به دنبال ماژولهای تک مسئولیتی باش"
در این قسمت مقصود از مسئولیت، «دلیلی است که کلاس باید تغییر کند» بدین معنا که اگر کلاسی با چند دلیل متفاوت مجبور به تغییر شود، آن کلاس چند مسئولیتی است. کلاسهای چند مسئولیتی عموما کد حجیمی دارند؛ نام آنها تعریف دقیقی را از مسئولیتشان ارائه نمیدهد و با عنوانی بسیار کلی نامگذاری میشوند و اشکال زدایی آنها بسیار طاقت فرساست. از طرفی، چند مسئولیتی بودن یک کلاس، باعث از بین رفتن مزایای توارث میشود. مثلا فرض کنید دو مسئولیت A,B در واسطی بیان میشوند که به یکدیگر مرتبط نبوده و مستقلند. برای مسئولیت A دو پیاده سازی و برای مسئولیت B، سه پیاده سازی در نظر گرفته شده است و جمعا برای پشتیبانی از تمامی حالات باید شش کلاس پیاده ساز، در نظر گرفته شود که توارث را سخت و بی معنی میکند زیرا قابلیت استفاده مجدد را از توارث سلب کرده است. با این وجود عملا رعایت همچین نکتهای در دنیای واقعی کار سختی است.
مثال زیر این مشکل را بیان میدارد:
// single responsibility principle - bad example interface IEmail { void SetSender(string sender); void SetReceiver(string receiver); void SetContent(string content); } class Email : IEmail { public void SetSender(string sender) { throw new NotImplementedException(); } public void SetReceiver(string receiver) { throw new NotImplementedException(); } public void SetContent(string content) { throw new NotImplementedException(); } }
در این مثال کلاس Email دارای دو مسئولیت (دلیل برای تغییر) است: الف- نحوه مقداردهی فرستنده و گیرنده براساس پروتکلهای مختلف مانند IMAP, POP3 ، بدین معنا که با تغییر پروتکل نیاز به تغییر پیاده سازی خواهیم شد. ب- تعریف محتوای پیام، بدین معنا که برای پشتیبانی از محتوای html, xml نیاز به تغییر کلاس Email داریم.
با تغییر طراحی خواهیم داشت:
// single responsibility principle - good example public interface IMessage { void SetSender(string sender); void SetReceiver(string receiver); void SetContent(IContent content); } public interface IContent { string GetAsString(); // used for serialization } public class Email : IMessage { public void SetSender(string sender) { throw new NotImplementedException(); } public void SetReceiver(string receiver) { throw new NotImplementedException(); } public void SetContent(IContent content) { throw new NotImplementedException(); } }
در اینجا واسط IContent مسئولیت پشتیبانی از xml, html را
خواهد داشت و نیازی به تغییر کلاس Email برای
پشتیبانی از این فرمتهای محتوای پیام را نخواهیم داشت.
اصل ششم: Open for
extension, close for modification : Open/Closed Principle
"پذیرای توسعه و
بازدارنده از تغییر هر آنچه که هست، باش"
ا ین اصل میگوید طراحی باید به گونهای باشد که با
اضافه شدن یک ویژگی، کدهای قبلی تغییری نکنند و فقط کدهای جدید برای پیاده سازی
ویژگی جدید نوشته شوند.
public class AreaCalculator { public double Area(object[] shapes) { double area = 0; foreach (var shape in shapes) { if (shape is Square) { Square square = (Square)shape; area += Math.Sqrt(square.Height); } if (shape is Triangle) { Triangle triangle = (Triangle)shape; double TotalHalf = (triangle.FirstSide + triangle.SecondSide + triangle.ThirdSide) / 2; area += Math.Sqrt(TotalHalf * (TotalHalf - triangle.FirstSide) * (TotalHalf - triangle.SecondSide) * (TotalHalf - triangle.ThirdSide)); } if (shape is Circle) { Circle circle = (Circle)shape; area += circle.Radius * circle.Radius * Math.PI; } } return area; } } public class Square { public double Height { get; set; } } public class Circle { public double Radius { get; set; } } public class Triangle { public double FirstSide { get; set; } public double SecondSide { get; set; } public double ThirdSide { get; set; } }
در اینجا کلاس AreaCalculator برای محاسبه مساحت تمام اشیاء ورودی، مساحت تک تک اشیاء را محاسبه میکند و نتیجه را برمیگرداند. در این مثال با اضافه شدن شکل هندسی جدید، باید کد این کلاس تغییر کند که با اصل Open/Closed مغایر است. برای بهبود این کد طراحی زیر پیشنهاد شده است:
public class AreaCalculator { public double Area(Shape[] shapes) { double area = 0; foreach (var shape in shapes) { area += shape.Area(); } return area; } } public abstract class Shape { public abstract double Area(); } public class Square : Shape { public double Height { get { return _height; } } private double _height; public Square(double Height) { _height = Height; } public override double Area() { return Math.Sqrt(_height); } } public class Circle : Shape { public double Radius { get { return _radius; } } private double _radius; public Circle(double Radius) { _radius = Radius; } public override double Area() { return _radius * _radius * Math.PI; } } public class Triangle : Shape { public double FirstSide { get { return _firstSide; } } public double SecondSide { get { return _secondSide; } } public double ThirdSide { get { return _thirdSide; } } private double _firstSide; private double _secondSide; private double _thirdSide; public Triangle(double FirstSide, double SecondSide, double ThirdSide) { _firstSide = FirstSide; _secondSide = SecondSide; _thirdSide = ThirdSide; } public override double Area() { double TotalHalf = (_firstSide + _secondSide + _thirdSide) / 2; return Math.Sqrt(TotalHalf * (TotalHalf - _firstSide) * (TotalHalf - _secondSide) * (TotalHalf - _thirdSide)); } }
در این طراحی، پیچیدگی محاسبه مساحت هر شکل به کلاس آن شکل منتقل شده است و با اضافه شدن شکل جدید نیازی به تغییر کلاس AreaCalculator نداریم.
در مقالهی بعدی به سه اصل دیگر اصول SOLID خواهم پرداخت.
کمی درباره httpmodule
public void Init(HttpApplication app) { app.AcquireRequestState+=new EventHandler(_AUTH); } private void _AUTH(object sender, EventArgs e) { HttpApplication app = (HttpApplication) sender; HttpContext context = app.Context; HttpSessionState session = context.Session; if(session!=null) { string text = "session is not exist"; if (session["userid"] != null) { text = "session is exist"; } context.Response.Write(text); } }
Owin چیست ؟ قسمت اول
NuGet چیست؟
روش متداول استفاده از کتابخانههای موجود دات نتی در Visual studio عموما به این صورت است: مراجعه به سایت مربوطه، دریافت بسته مورد نظر، باز کردن آن و سپس افزودن ارجاعی به اسمبلیهای آن کتابخانه. در این حالت زمانیکه نسخهی جدیدی از کتابخانهی مورد استفاده ارائه شود (و عموما تا مدتها شاید از آن بیاطلاع باشیم) تمام این مراحل باید از ابتدا تکرار شوند و همینطور الی آخر.
برای رفع این نقیصه، تیم ASP.NET، افزونهای سورس باز و رایگان را به نام NuGet جهت VS.Net 2010 طراحی کردهاند که کار مدیریت بستههای کتابخانههای مورد استفاده را بسیار ساده کرده است. امکانات این افزونه پس از نصب، در دو حالت استفاده از رابط گرافیکی کاربری آن و یا با استفاده از خط فرمان PowerShell ویندوز در دسترس خواهد بود. این افزونه در زمان بارگذاری، با مراجعه به فید سایت مرکزی خود، لیست بستههای مهیا را در اختیار علاقمندان قرار میدهد. درب این سایت مرکزی به روی تمام توسعه دهندهها جهت افزودن بستههای خود باز است.
و ... فراگیری کار با NuGet برای تمام برنامه نویسان دات نت لازم و ضروری است! از این جهت که پیغام "این بسته تنها برای NuGet عرضه شده است" کم کم در حال متداول شدن میباشد و دیگر سایتهای مرتبط، لینک مستقیمی را جهت دریافت کتابخانههای خود ارائه نمیدهند. حتی خبر به روز شدن محصولات خود را هم شاید دیگر به صورت منظم ارائه ندهند؛ زیرا NuGet کار مدیریت آنها را به عهده خواهد داشت.
دریافت و نصب NuGet
NuGet را حداقل به سه طریق میتوان دریافت و نصب کرد:
الف) با مراجعه به سایت CodePlex : (+)
ب) دریافت آن از سایت گالریهای آن : (+)
ج) با استفاده از امکانات VS.NET
هر سه روش فوق به دریافت و نصب فایل NuGet.Tools.vsix منتهی میشوند. برای مثال در روش (ج) باید به منوی Tools و گزینهی Extension Manager مراجعه کنید. سپس برگهی Online Gallery را گشوده و اندکی صبر کنید تا اطلاعات آن دریافت و نمایش داده شود. سپس NuGet را در Search box بالای صفحه نوشته و NuGet Package manager ظاهر شده را انتخاب و نصب کنید.
نحوه استفاده از NuGet
فرض کنید یک پروژه جدید ASP.NET را ایجاد کردهاید و نیاز است تا کتابخانهی ELMAH به آن اضافه شود. روش انجام اینکار را به کمک NuGet در ادامه بررسی خواهیم کرد (کمتر از یک دقیقه زمان خواهد برد):
الف) با کمک امکانات رابط گرافیکی کاربر آن
سادهترین روش استفاده از NuGet ، کلیک راست بر روی پوشه References در Solution explorer و سپس انتخاب گزینهی Add Library Package Reference میباشد:
در صفحهی باز شده، برگهی Online را باز کنید و مدتی صبر نمائید تا اطلاعات لازم دریافت گردد (در زمان نگارش این مطلب، 1135 بسته در این مخزن موجود است):
سپس در جعبهی جستجوی سمت راست بالای صفحه، نام کتابخانهی مورد نظر را نوشته و اندکی صبر کنید تا اطلاعات آن نمایش داده شود:
اکنون با کلیک بر روی دکمه Install ، بسته مرتبط با این کتابخانه دریافت شده و سپس به صورت خودکار ارجاعی به آن نیز افزوده خواهد شد. همچنین تنظیمات مرتبط با فایل Config برنامه هم اضافه میشوند.
روش دیگر ظاهر کردن این صفحه، مراجعه به منوی Tools و گزینهی Library Package Manager میباشد:
جهت دریافت به روز رسانیهای بستههای نصب شده تنها کافی است به برگهی Updates این صفحه مراجعه کرده و موارد لیست شده را نصب نمائیم:
نکته: NuGet در SharpDevelop 4.1 به بعد هم پشتیبانی میشود:
ب) با استفاده از امکانات خط فرمان PowerShell ویندوز
برای استفاده از امکانات پاورشل ویندوز نیاز است تا پاورشل نگارش 2 بر روی سیستم شما نصب باشد (نیاز به Windows XP with Service Pack 3 به بعد دارد). سپس به منوی Tools ، قسمت Library Package Manager ، گزینهی Package Manager Console آن جهت فعال سازی کنسول پاور شل در VS.NET مراجعه نمائید:
نکته: در تصویر فوق پس از نوشتن el ، دکمه tab فشرده شده است. در این حالت منوی پکیجهای مهیای شروع شده با el، از سایت مرکزی NuGet ظاهر گردیده است.
فرامین مهمی که در اینجا در دسترس هستند شامل: List-Package ، Uninstall-Package ، Update-Package و Get-Package میباشند. برای مثال اگر قصد جستجو در بین بستههای موجود را داشته باشید Get-Package بسیار مفید است:
برای مثال جهت یافتن بستههای مرتبط با wpf و silverlight به صورت زیر میتوان عمل کرد:
PM> get-package -remote -filter wpf
PM> get-package -remote -filter silverlight
نکته: روش دیگر جستجو در بین بستههای مهیا، مراجعه به سایت گالری آن است : (+) . در اینجا دستور پاورشل نصب هر بستهی یافت شده نیز نمایش داده میشود.
ج) استفاده از برنامه NuGet.exe
برنامه NuGet.exe از سایت CodePlex قابل دریافت است. این روش هم جهت علاقمندان به خط فرمان تدارک دیده شده است!
پس از دریافت آن فرض کنید میخواهیم تمام بستههای شروع شده با nhi را لیست کنیم. برای این منظور دستور خط فرمان ذیل را صادر کنید:
D:\Test>nuget list nhi
D:\Test>nuget install NHibernate
به این صورت کتابخانه NHibernate به همراه تمام وابستگیهای آن دریافت خواهد شد.
به روز رسانی خودکار NuGet
برای به روز رسانی برنامه nuget.exe دستور زیر را میتوان صادر نمود:
D:\Test>NuGet.exe u
Tools | Options, then Environment | Extension Manager and click "Automatically check for updates to installed extensions."
ادامه دارد ...
نکته : آشنایی اولیه با مفاهیم WCF جهت درک صحیح مطالب الزامی است.
تشریح مسئله : در صورتی که نیاز باشد که نمونه ساخته شده از سرویس (سمت سرور) به صورت Singleton باشد بهترین روش برای پیاده سازی به چه صورت است.
برای شروع ابتدا مثال زیر را پیاده سازی میکنیم.
یک Contract به صورت زیر تعریف میکنیم:
[ServiceContract(SessionMode=SessionMode.Allowed)] public interface IMyService { [OperationContract] int GetData(); }
حالا یک سرویس برای پیاده سازی Interface بالا مینویسیم.
[ServiceBehavior( InstanceContextMode = InstanceContextMode.PerCall )] public class PerCallService : IMyService { int count; public int GetData() { return ++count; } }
حالا برای مشاهده نتیجه یک پروژه ConsoleApplication ایجاد کنید و سرویس مورد نظر را از روش AddServiceReference به پروژه اضافه کرده در فایل Program کدهای زیر را کپی کنید.
static void Main( string[] args ) { Console.WriteLine( "PerCall Service" ); MyPerCallService.MyServiceClient client = new MyPerCallService.MyServiceClient(); int count = 0; for ( int i = 0 ; i < 5 ; i++ ) { count = client.GetData(); } Console.WriteLine( count ); Console.ReadLine(); }
بعد از 5 بار فراخوانی متد GetData باز خروجی دارای مقدار 1 است. یعنی به ازای هر بار فراخوانی متد GetData یک نمونه از سرویس مورد نظر ساخته میشود.این عمل توسط خصوصیت InstanceContextMode که از نوع PerCall است به سرویس اعمال میشود.
حالا یک سرویس دیگر به صورت زیر ایجاد کنید.
[ServiceBehavior( InstanceContextMode = InstanceContextMode.Single )] public class SingleService : IMyService { int count; public int GetData() { return ++count; } }
کد کلاس Program رو به صورت زیر تغییر دهید.
static void Main( string[] args ) { Console.WriteLine( "Single Service" ); MySingleService.MyServiceClient client = new MySingleService.MyServiceClient(); int count = 0; for ( int i = 0 ; i < 5 ; i++ ) { count = client.GetData(); } Console.WriteLine("Result is : {0}", count ); Console.ReadLine(); }
به ازای 5 بار فراخوانی سرویس متغیر Count سمت سرور مقدار قبلی خود را حفظ کرده است.
ASP.NET MVC #12
در خصوص این مبحث یک سوالی برایم پیش آمد:
همانطور که فرمودید در ساخت View های مختلف میتوان از Helper های مختلف استفاده کرد. مثلا Html.DropDownListFor یا BeginForm و غیره ...
نهایتاً تمام این ها سمت سرور اجرا شده و نتیجه HTML برمیگرداند. حال به نظر شما مشکلی دارد پس از آنکه با استفاده از Helper های مختلف View نهایی تولید شد و دیگر قصد تغییر آن را نداشتیم، نتیجۀ HTML شده را جهت استفاده نهایی استفاده کنیم، یعنی در Vew نهایی که بر روی Host آپلود می شود، کد های HTML تولید شده در مرحله قبل را قرار دهیم.
با این کار دیگر لازم نیست سمت سرور Viewها ساخته شوند و همه چیز آماده است؛ که میتواند باعث افزایش سرعت شود. آیا درست متوجه شدم؟
البته قسمت هایی مثل ActionLink ها که با توجه به موقعیت جاری ساخته میشوند به حالت قبل در View نوشته شوند.
آشنایی با NUnit
NUnit یکی از فریم ورکهای آزمایش واحد سورس باز مخصوص دات نت فریم ورک است. (کلا در دات نت هرجایی دیدید که N ، به ابتدای برنامهای یا کتابخانهای اضافه شده یعنی نمونه منتقل شده از محیط جاوا به دات نت است. برای مثال NHibernate از Hibernate جاوا گرفته شده است و الی آخر)
این برنامه با سی شارپ نوشته شده است اما تمامی زبانهای دات نتی را پشتیبانی میکند (اساسا با زبان نوشته شده کاری ندارد و فایل اسمبلی برنامه را آنالیز میکند. بنابراین فرقی نمیکند که در اینجا چه زبانی بکار گرفته شده است).
ابتدا NUnit را دریافت نمائید:
http://nunit.org/index.php?p=download
یک برنامه ساده از نوع console را در VS.net آغاز کنید.
کلاس MyList را با محتوای زیر به پروژه اضافه کنید:
using System.Collections.Generic;
namespace sample
{
public class MyList
{
public static List<int> GetListOfIntItems(int numberOfItems)
{
List<int> res = new List<int>();
for (int i = 0; i < numberOfItems; i++)
res.Add(i);
return res;
}
}
}
اکنون بر روی نام پروژه در قسمت solution explorer کلیک راست کرده و گزینه add->new project را انتخاب کنید. نوع این پروژه را که متدهای آزمایش واحد ما را تشکیل خواهد داد، class library انتخاب کنید. با نام مثلا TestLibrary (شکل زیر).
با توجه به اینکه NUnit ، اسمبلی برنامه (فایل exe یا dll آنرا) آنالیز میکند، بنابراین میتوان پروژه تست را جدای از پروژه اصلی ایجاد نمود و مورد استفاده قرار داد.
پس از ایجاد پروژه class library ، باید ارجاعی از NUnit framework را به آن اضافه کنیم. به محل نصب NUnit مراجعه کرده (پوشه bin آن) و ارجاعی به فایل nunit.framework.dll را به پروژه اضافه نمائید (شکل زیر).
سپس فضاهای نام مربوطه را به کلاس آزمایش واحد خود اضافه خواهیم کرد:
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
سپس باید ویژگی جدیدی به نام TestFixture را به این کلاس اضافه کرد.
[TestFixture]
public class TestClass
سپس هر متدی که به عنوان متد آزمایش واحد نوشته میشود، باید دارای ویژگی Test باشد تا توسط NUnit بررسی گردد:
[Test]
public void TestGetListOfIntItems()
اکنون برای اینکه بتوانیم متد GetListOfIntItems برنامه خود را در پروژه دیگری تست کنیم، باید ارجاعی را به اسمبلی آن اضافه کنیم. همانند قبل، از منوی project گزینه add reference ، فایل exe برنامه کنسول خود را انتخاب کرده و ارجاعی از آنرا به پروژه class library اضافه میکنیم. بدیهی است امکان اینکه کلاس تست در همان پروژه هم قرار میگرفت وجود داشت و صرفا جهت جداسازی آزمایش از برنامه اصلی اینکار صورت گرفت.
پس از این مقدمات، اکنون متد آزمایش واحد ساده زیر را در نظر بگیرید:
[Test]
public void TestGetListOfIntItems()
{
const int count = 5;
List<int> items = sample.MyList.GetListOfIntItems(count);
Assert.That(items.Count,Is.EqualTo(5));
}
اگر آن را (سطر مربوط به Assert را) کلمه به کلمه بخواهیم به فارسی ترجمه کنیم به صورت زیر خواهد بود:
میخواهیم اثبات کنیم که count مربوط به شیء items مساوی 5 است.
پس از اضافه کردن متد فوق، پروژه را کامپایل نمائید.
اکنون برنامه nunit.exe را اجرا کنید تا NUnit IDE ظاهر شود (در همان دایرکتوری bin مسیر نصب NUnit قرار دارد).
از منوی File آن یک پروژه جدید را آغاز نموده و آنرا ذخیره کنید.
سپس از منوی project آن، با استفاده از گزینه add assembly ، فایل dll کتابخانه تست خود را اضافه نمائید.
احتمالا پس از انجام این عملیات بلافاصله با خطای زیر مواجه خواهید شد:
---------------------------
Assembly Not Loaded
---------------------------
System.ApplicationException : Unable to load TestLibrary because it is not located under
the AppBase
----> System.IO.FileNotFoundException : Could not load file or assembly
'TestLibrary' or one of its dependencies. The system cannot find the file specified.
For further information, use the Exception Details menu item.
همانطور که ملاحظه میکنید، NUnit با استفاده از قابلیتهای reflection در دات نت، اسمبلی را بارگذاری میکند و تمامی کلاسهایی که دارای ویژگی TestFixture باشند در آن لیست خواهد شد.
اکنون بر روی دکمه run کلیک کنید تا اولین آزمایش ما انجام شود. (شکل زیر)
رنگ سبز در اینجا به معنای با موفقیت انجام شدن آزمایش است.
ادامه دارد...