Wednesday, March 08, 2006

Unit Testing Using NUnit

بسم الله الرحمن الرحيم

هل سمعت عن الTesting ؟؟؟؟
بالطبع فال
Testing هو المرحلة قبل الأخيرة في دورة حياة تطوير البرنامج Software Development Life Cycle SDLC
و هي المرحلة التي تلي مرحلة ال
Development مباشرة

ما أهميتها؟؟؟
أهميتها بديهية و هي تجنب أخطاء قد تحدث أثناء استخدام البرامج و التي قد تؤدي لخسائر ضخمة مثل أنظمة البنوك و ربما خسائر في الأرواح لو كان هذا البرنامج حيوى جداً مثل أنظمة الطائرات الملاحية و غيرها
و أهمية أخرى هي ضمان مستوى معين من جودة البرامج و توجد مقاييس عالمية مثل ال
CMMI و غيرها

هل المبرمج مطالب بتعلم الTesting ؟؟
ال
Testing في اطلاقه فرع كامل و كبير من فروع الSoftware و يقع تحت الQuality Assurance أو ضمان الجودة و يوجد له متخصصين مثل الTest Engineers و الQA Specialists و غيرهم

هل يعني هذا أنه لا فائدة من تعلمه بالنسبة للمبرمج؟؟؟
بالطبع معرفة المبادئ الأساسية في ال
Testing شئ أساسي و هذا لأنك في النهاية ستعمل جنباً بجنب مع Tesers و أحياناً في بعض الMethodologies مثل Scrum أو Extreme Programming قد تجلس أنت كDeveloper مع Tester على جهاز واحد لضمان اكتشاف الأخطاء أثناء كتابتها...

ماهو الجزء الذي يُهم المبرمج؟؟؟
الجزء الذي يُهم المبرمج و الذي يكون هو مُطالب بأداءه هو ال
Unit Testing و هو جزء صغير من الكود يقوم المبرمج بكتابته لعمل Testing لجزء اخر أكبر من الكود
و لنفهم ذلك تابع هذا المثال...
بفرض أنك تقوم بكتابة
Class تقوم بعمليات حسابية جمع و طرح مثلاً

CODE

public class MathOps
{
public static int Add(int x, int y)
{
return x + y;
}

public static int Sub(int x, int y)
{
return x - y;
}
}


و تريد أن تتأكد من صحة العمليات فماذا ستفعل؟؟؟
أغلبنا سيقوم بعمل
Application بسيط (Console Application مثلاً) و يقوم باستخدام الClass و باعطاء قيم input و قراءة الoutputs و مقارنتها بالنتيجة الصحيحة المتوقعة Expected Result
هل هذه طريقة خطأ؟؟؟
لا بل ستؤدي المطلوب

ما عيوب هذه الطريقة؟؟؟
1- لا تصلح في المشاريع الضخمة التي تحتوي على عشرات ال
Classes و الMethods
2- كود ال
Testing منفصل تماماً عن الكود الحقيقي مما يجعل المشكلة الكبرى هي ربط كل class بالapplication الخاص بتجربتها و هذا يؤدي الى مشكلة تنظيمية أكبر
3-لا يمكنك عمل
Testing للPrivate Classes بسهولة و هذا لأن الكود الخاص بالTesting منفصل
4- ستحتاج كل مرة لعمليتين
Compile مرة للClasses الأساسية سواء كانت في dll منفصل او اي شئ اخر و مرة أخرى للTesting Application و هذا يُهدر وقت و مجهود

هل توجد طريقة أخرى؟؟؟
بالطبع و هي موضوع هذا المقال و هي ال
Unit Testing

مميزات الUnit Testing
1- تضمين كود ال
Testing داخل الUnit أو الDll التي تحتوي على الكود الأصلي و هذا يجعل الحل Modular بحيث لن تحتاج لحفظ الTesting Code منفصلاً عن الdll
2- سرعة أكثر في انجاز عملية ال
Unit Testing

اذن كيف يمكننا كتابة class تقوم بعمل testing للMathOps Class التي كتبناها منذ قليل
الموضوع بسيط جداً

CODE

[TestFixture]
public class TestClass
{

[SetUp]public void Init()
{

}

[Test]public void TestAdd()
{
int result = MathOps.Add(10, 20);
Assert.AreEqual(result, 30);
}
[Test]public void TestSub()
{
int result = MathOps.Sub(20, 10);
Assert.AreEqual(result, 10);
}
}


يمكنك اهمال ال
Attributes الموجودة للحظات سنتطرق لها بعد عدة سطور...
كما ترى أن ال
Class بسيطة فأنت تكتب فيها مجموعة من الFunctions تقوم بعملية Testing لFunction من الMathOps Class عن طريق اعطاءها قيم كمدخلات و مقارنة المخرجات مع القيم المتوقعة ثم عمل Assertion على أساس شرط معين و هنا الشرط هو أن تساوي القيم المتوقعة مع المخرجات
يمكن أن تكون عملية ال
Testing تقيس تعامل البرنامج مع الأخطاء فمثلاً في عملية القسمة تريد أن تتأكد أنه اذا كان المقسوم عليه يساوي صفر فان الFunction ستطلق ZeroDivisionException مثلاً و في هذه الحالة نعتبر الTestصحيحاً

ما هي الأدوات المستخدمة في الUnit Testing ؟؟؟؟
لكل لغة أو تكنولوجيا الأدوات الخاصة بها فمثلاً في ال
Java يوجد الJUnit و في الDotNet يوجد الNUnit و هو مجال حديثنا في الجزء الثاني من المقال

NUnit
هو مشروع مفتوح المصدر من أحد المشاريع الموجودة في
www.sourceforge.net و هو مجاني و يدعم جميع لغات الDotNet سواء في الاصدارة 1 أو 1.1 أو 2.0
و يمكنك تحميله من على http://www.nunit.com

كيف نستخدم Nunit
اولاً تحميل و تثبيت
Nunit بسيط فقط حمل الNunit Installer من على الموقع و السابق ثم ثبته ببساطة
ثانياً ستقوم باضافة ال
dll التالية لمشاريعك التي تنوي عمل testing لها و هي nunit.framework.dll و ستجدها موجودة عندما تقوم بعمل Add Reference من داخل الVisual Studio
ثالثاً قم بعمل

CODE

using NUnit.Framework;

في الملف الذي تنوي أن تكتب فيه الTesting Class
رابعاً استخدم ال
Attributes التالية
TestFixture و يستخدم قبل الTesting Class
SetUp و يستخدم قبل الinitialization method و التي تستخدمها عادة لعمل أي initializations
Test و يستخدم قبل كل Test Method
Ignore و يستخدم لتجاهل Test Method معينة
يوجد العديد من ال
Attributes الأخرى يمكنك استكشافها بنفسك و لكن سنكتفي هنا بتجربة هذه الAttributes

CODE

[TestFixture]
public class TestClass
{

[SetUp]public void Init()
{

}

[Test]public void TestAdd()
{
int result = MathOps.Add(10, 20);
Assert.AreEqual(result, 30);
}
[Test]public void TestSub()
{
int result = MathOps.Sub(20, 10);
Assert.AreEqual(result, 10);
}
}


سيكون هذا هو شكل ال
Class التي سنستخدمها في الTesting
سنقوم الان بعمل
Build للSolution ثم نفتح الNunit (ستجد له رابط على الDesktop بعد التثبيت)
سيظهر لنا بهذا الشكل

user posted image
سنقوم بعدها بعمل File->Open ثم نبحث عن البرنامج الذي قمنا بكتابته سواء كان dll أو exe
سيظهر لنا بهذا الشكل
user posted image
سنجد أن القائمة على اليسار تظهر لنا الClass التي وضعنا قبلها الTestFixture Attribute و أيضاً تظهر لنا الMethods التي وضعنا قبلها الTest Attribute
قم بالضغط على
Run
سيظهر لك هذا الشكل و الذي يبين أن عملية ال
Testing قد نجحت و الكود سليم

user posted image
و لكن لو أحدى العمليات فشلت سيظهر لك هذا الشكل
و الذي يبين لك أن العمليات التي نجحت و العمليات التي فشلت و يوضح لك سبب الخطاً
user posted image
و الان تخيل معي انك تعمل في مشروع ضخم و تقوم بعمليات تعديل في Methods موجودة بالفعل و تغير في طريقة عملها و لكن في النهاية المهم أن تكون النتائج سليمة فكل ماعليك هو تشغيل الTest كل مرة تغير أو تعدل في الكود للتأكد من أن كل شئ على ما يرام ثم تكمل عملك و أنت واثق

ال
Nunit كمشروع ناجح و لاقى شعبية جيدة تم تضمينه في SharDevelop داخل الIDE كما أن الTesting Environment الموجودة في Visual Studio 2005 Team System مستوحاة من Nunit

ارجو أن يكون الموضوع بسيط و سهل و واضح

Friday, March 03, 2006

Three Tier Model

أغلبنا سمع عن الThree Tier Model و الذي يتكون من الأجزاء الثلاثة الشهيرة
1- ال
Data Tier
2- ال
Business Logic Tier
3- ال
Presentation Tier


و هذا هو النظام الأشهر في بناء التطبيقات التجارية و سنحاول شرحه ببساطة و اعطاء أمثلة ان شاء الله

1- ال Data Tier
و هي الجزء الذي يتولى التعامل مع مصدر المعلومات مهما كان نوعه (قاعدة بيانات, ملفات نصية , .... الخ) و هي التي تتولى تخزين و استرجاع البيانات بغض النظر عن النظام نفسه و ماذا يفعل
و ينقسم هذا الجزء الى قسمين أصغر
أ- Data Access Layer و هي الجزء المسؤول عن التعامل المباشر مع قاعدة البيانات فهي تأخذ الQuery أو الStored Procedure و تنفذها و تعود بالناتج بدون تفكير و يمكن ببساطة نقل هذا الجزء من نظام لآخر دون تغيير في الكود الخاص بها ... و مثال بسيط عليها هو ال ADO.net نفسه
ب- Proxy Layer
و هي
Classes تنفذ الوظائف الأساسية على الTables مثل الUpdate و الSelect و الDelete و الInsert و لكل Table له Class Proxy خاصة به تتولى العمليات الخاصة به
بمعنى أنه اذا فرضنا أننا نملك
Database تحوي Tables بهذه الأسماء Users, Articles ...
و هكذا سيكون لدينــا 2
Classes من نوع Proxy و كل منهم يحوي الCode اللازم لاضافة و حذف و تعديل و قراءة المعلومات من الجدول المناظر له في الDatabase
و لكن يظهر سؤال ماهي الطريقة التي سنمثل بها ال
Data خارج الDatabase أي عندما نقوم بعمل شئ مشابه لهذا

CODE


Proxy_Users user = new Proxy_Users();
user.GetUser(2)


فكيف سنخزن ال
User Data و أين ؟؟؟
يجب أن يكون هناك مخزن للمعلومات و هنــا يمكن استخدام ال
Strongly Typed DataSets مثلاً أو أن تقوم بعمل Class تمثل كل Table من اختيارك بدون الاعتماد على ال STDataSets
و سنأتي لهذا الموضوع لاحقاً

2- الBusiness Tier
و هي المنطقة التي يتحول فيها النظام من كونه
Tables مخزنة في Database الى Object Oriented Model أي أننا بعد كتابة هذا الجزء يمكننا أن ننسى تماماً طبيعة تخزين البيانات..
كمثال على ال
Database السابقة سيكون لدينا Class User و Class Article و داخل ال Article يوجد reference على Object من نوع User تمثل كاتب المقال و أيضاً داخل الUser يوجد Collection من الArticle Objects تمثل المقالات التي تخص هذا المستخدم و هكذا يمكننا استخدام الClasses بهذه الطريقة

CODE

User user = new User();
user.FirstName = "Mohammed";
user.LastName = "Ahmed";
user.Age = 12;
user.Save();
Article article = new Article();
article.Subject = "anyThing";
article.CreationDate = DateTime.Now;
article.Author = user;
article.Save();
user.Articles.Add(article);


و هكذا تجد أن الكود أصبح أكثر قوة و أسهل في التعامل و أصبح ايضاًً
Encapsulated بشكل متميز و استطعنا اخفاء تفاصيل التعامل مع الDatabase في الtiers الأدنى و أيضاً داخل ال BusinessObjects استطعنا اخفاء استخدامنا للDataTier حتى يمكننا في الLayer الأعلى أن ننسى فعليا طبيعة البيانات

3- الPresentation Tier
و هي الجزء الذي يتعامل مع مستخدم البرنامج (من الاخر ال
User interface ) بجميع أنواعه و البيئات المستخدمة سواء كان Windows Application أو Web Application أو حتى Console Applications و يتم في هذا الجزء التعامل المباشر مع الBusinessTier و لا يُسمح أبدا بالتعامل المباشر مع الDataTier و في الحقيقة لو كان تصميمك جيداً فلن تحتاج أبدأ الDataTier في الPresentation Tier

و كمثال على تصميم لData Tier
افترض وجود
Database بهذا التصميم
بمعنى أنه اذا فرضنا أننا نملك
Database تحوي Tables بهذه الأسماء User, Article and Category


user posted image


يمكننا أن نقول أن كل
Proxy Class تتكون من Base Class و Child Class
ال
Base Class تحدد الشكل الأساسي للClass و الChild يحدد الImplementation الخاص بهذه الClass على نوع محدد من الData Sources بمعنى أنه اذا اردنا أن نجعل الApplication يعمل على ال SqlServer سنقوم بعمل Inheritance من ال Proxy Base و عمل SqlProxy Child مخصص للSql Server
و هذا يسهل التحول من من
Data Source الى اخر مجرد اعادة عمل الInheritance من الPorxyBase لعمل مثلاً OracleProxy و تحديد الImplementation الخاص بالOracle


user posted image


هذا مثال على
Proxy Base Class

CODE

using System.Collections;
using DataAccessLayer;
using Info_Tier;

namespace Data_Tier
{
public abstract class Proxy_Article
{
protected DAL m_conn;
protected string m_strConn;

public Proxy_Article()
{
m_strConn = Connection.GetConnectionString();
m_conn = new DAL(DbProvider.SqlServer, m_strConn);
}

public abstract int ArticleAdd(Info_Article info_Article);
public abstract void ArticleModifyBySID(int SID, Info_Article info_Article);
public abstract ArrayList ArticleGetAll();
public abstract void ArticleDelete(int SID);
public abstract Info_Article ArticleGet(int SID);
public abstract ArrayList ArticleGetByPerson_SID(int Person_SID);
}

}




و يكون هذا شكل الSqlProxy

CODE

using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using Info_Tier;

namespace Data_Tier
{
public class SqlProxy_Article : Proxy_Article
{
public SqlProxy_Article()
{
}

public override int ArticleAdd(Info_Article info_Article)
{
SqlParameter[] parameters = new SqlParameter[5];
parameters[0] = new SqlParameter("@SID", info_Article.SID);
parameters[0].Direction = ParameterDirection.Output;
parameters[1] = new SqlParameter("@Subject", info_Article.Subject);
parameters[1].Direction = ParameterDirection.Input;
parameters[2] = new SqlParameter("@Details", info_Article.Details);
parameters[2].Direction = ParameterDirection.Input;
parameters[3] = new SqlParameter("@MadeOn", info_Article.MadeOn);
parameters[3].Direction = ParameterDirection.Input;
parameters[4] = new SqlParameter("@Person_SID", info_Article.Person_SID);
parameters[4].Direction = ParameterDirection.Input;
m_conn.ExecuteStoredProcedureNonQuery("spArticleAdd", parameters);
return (int) parameters[0].Value;

}


public override void ArticleModifyBySID(int SID, Info_Article info_Article)
{
SqlParameter[] parameters = new SqlParameter[5];
parameters[0] = new SqlParameter("@SID", info_Article.SID);
parameters[1] = new SqlParameter("@Subject", info_Article.Subject);
parameters[2] = new SqlParameter("@Details", info_Article.Details);
parameters[3] = new SqlParameter("@MadeOn", info_Article.MadeOn);
parameters[4] = new SqlParameter("@Person_SID", info_Article.Person_SID);
m_conn.ExecuteStoredProcedureNonQuery("spArticleUpdateBySID", parameters);

}

public override ArrayList ArticleGetAll()
{
ArrayList info_array = new ArrayList();
IDataReader reader = m_conn.ExecuteStoredProcedureReader("spArticleGetAll", null);
while (reader.Read())
{
Info_Article info = new Info_Article();
info.SID = (int) reader["SID"];
info.Subject = reader["Subject"].ToString();
info.Details = reader["Details"].ToString();
info.MadeOn = DateTime.Parse(reader["MadeOn"].ToString());
info.Person_SID = (int) reader["Person_SID"];
info_array.Add(info);

}
return info_array;

}

public override void ArticleDelete(int SID)
{
m_conn.ExecuteStoredProcedureNonQuery("spArticleDeleteBySID", new SqlParameter[] {new SqlParameter("@SID", SID)});

}

public override Info_Article ArticleGet(int SID)
{
Info_Article info = new Info_Article();
IDataReader reader = m_conn.ExecuteStoredProcedureReader("spArticleGetBySID", new SqlParameter[] {new SqlParameter("@SID", SID)});
if (reader.Read())
{
info.SID = (int) reader["SID"];
info.Subject = reader["Subject"].ToString();
info.Details = reader["Details"].ToString();
info.MadeOn = DateTime.Parse(reader["MadeOn"].ToString());
info.Person_SID = (int) reader["Person_SID"];
}
return info;

}

public override ArrayList ArticleGetByPerson_SID(int Person_SID)
{
ArrayList info_array = new ArrayList();
IDataReader reader = m_conn.ExecuteStoredProcedureReader("spArticleGetByPerson_SID", new SqlParameter[]{new SqlParameter("@Person_SID", Person_SID)});
while (reader.Read())
{
Info_Article info = new Info_Article();
info.SID = (int) reader["SID"];
info.Subject = reader["Subject"].ToString();
info.Details = reader["Details"].ToString();
info.MadeOn = DateTime.Parse(reader["MadeOn"].ToString());
info.Person_SID = (int) reader["Person_SID"];
info_array.Add(info);

}
return info_array;
}

}

}



و ستلاحظ في ال
Codes استخدام Objects من نوع Info_Article و هي النقطة التي تكلمنا عنها في شرح الDataTier في الدرس السابق عندما ذكرنا استخدام الStrongly Typed DataSets في تخزين البيانات في الذاكرة أو عمل Class من اختيارك لتمثيل شكل الTable و هذه الطريقة الأخيرة هي المستخدمة فقد قمت بعمل Class تُسمى Info_Article تمثل الSchema الخاصة بالArticle Table و كل Object منها يمثل صف واحد من المعلوماتد
و هذا هو شكل ال
Info_Article Class


CODE

using System;

namespace Info_Tier
{
public class Info_Article
{
private int m_SID;
private string m_Subject;
private string m_Details;
private DateTime m_MadeOn;
private int m_Person_SID;

public int SID
{
set { this.m_SID = value; }
get { return this.m_SID; }
}

public string Subject
{
set { this.m_Subject = value; }
get { return this.m_Subject; }
}

public string Details
{
set { this.m_Details = value; }
get { return this.m_Details; }
}

public DateTime MadeOn
{
set { this.m_MadeOn = value; }
get { return this.m_MadeOn; }
}

public int Person_SID
{
set { this.m_Person_SID = value; }
get { return this.m_Person_SID; }
}

}
}



و هكذا يكون لكلل
Table لدينا في الDatabase هذه الClasses
1- ال
Base Proxy Class مثال الProxy_Article
2- ال
Child Specific Class مثال الSqlProxy_Article
3- ال
Info Class مثال ال Info_Article

و كما ذكرنا ال
Info_Article هي مجرد مخزن بيانات و الobject منه يماثل Row أو صف واحد من البيانات في الDatabase من هذا النوع و هكذا يمكننا تمثيل الQuery Result في هيئة Array of Info Objects و لهذا ستجد أن الدالة GetAll في ال Proxy_Article الناتج منها ArrayList و هو في الحقيقة Array of Info_Article

ثم نعود و نسأل عندما أحب أن أنشئ
object من الProxy فماذا أفعل ؟؟؟
في هذه الحالة التي يكون لديك
base abstract class و مجموعة من الchild classes و سيتم استخدام الbase class كpointer على أي من الchild objects فان الحل دائما و أبدأً يكون استخدام الDesign Pattern الشهير Factory
و هو أن تصنع
Class أخرى مهمتها انشاء الProxy Classes من النوع المطلوب و ارجاع pointer من نوع الparent class
و يكون الكود في هذه الحالة لل
Factory Class


CODE

public class Factory
{
public static Proxy_Article GetArticleProxy()
{
if ( certain condition)
return new SqlProxy_Article();
return new OracleProxy_Article();
}
}



حيث ال
Certain Condition هو الشرط الذي يحدد نوع الDatabase التي أتخاطب معها فربما كان هذا الشرط قيمة في الRegistry أو ملف Config أو اي شئ اخر
و هكذا عندما أحتاج لاستخدام ال
Proxy فان الكود يكون بسيط

CODE


Proxy_Article proxyArticle = Factory.GetArticleProxy();



و أخيراً قبل أن ننهي شرح ال
DataAccess Layer
ماذا عن ال
Connection String من سيكون المسؤول عنه
ببساطة سنقوم بعمل
Class اسمها Connection تحوي function واحدة فقط static و هي GetConnectionString و تكون هذه الfunction مسؤولة عن احضار الConnection String سواء كان مخزن HardCoded أو في ملف خارجي أو مُشفر في ملف أو في الRegistry و يكون الكود على هذا الشكل مثلاً


CODE

public class Connection
{
public static string GetConnectionString()
{
if(certain condition)
return "Data Source=(local); Database = Northwind; Integrated Security = SSPI;
return "Oracle Connection String ";
}
}


و هكذا يمكننا تغيير ال
Database الخاصة بالبرنامج في ثواني بدون اللجوء الى اعادة كتابة الCode كله كل ما سيتم في هذه الحالة
كتابة ال
Proxy الخاص بالDatabase الجديدة و تغيير الConnection String و ستكون الDataTier جاهزة للتعامل مع الDatabase الجديدة و لن نحتاج لتغيير سطر واحد في الطبقات الأعلى و بفرض أن هذه الطبقة على شكل dll سيتم استبدال الdll الخاصة بالتعامل مع الSql Server بالdll الخاصة بالOracle و ما يمكن ببعض التفاصيل البسيطة الاستغناء عن هذا

و للحديث بقية في ال
Business Tier