2. تكامل Spring / NHibernate
يوفر إطار عمل Spring فئات مساعدة للعمل مع إطار العمل NHibernate. يجعل استخدام هذه الفئات كتابة كود الوصول إلى بيانات SGBD أسهل. لنفكر في البنية متعددة الطبقات التالية:
![]() |
في ما يلي، سنقوم ببناء طبقة [dao] باستخدام [Spring / NHibernate] من خلال شرح كود حل وظيفي. لن نسعى إلى تقديم جميع إمكانيات تكوين أو استخدام إطار العمل [Spring / Nhibernate]. يمكن للقارئ تكييف الحل المقترح مع مشكلاته الخاصة بالاستعانة بوثائق Spring.NET [Spring.NET | Homepage ] (ديسمبر 2011).
2.1. طبقة الوصول إلى البيانات [dao]
![]() |
قاعدة البيانات هي قاعدة MySQL [dbpam_nhibernate] التي تم عرضها بالفعل في الفقرة 1.2. طبقة [dao] تنفذ واجهة C# التالية:
using Pam.Dao.Entites;
namespace Pam.Dao.Service {
public interface IPamDao {
// قائمة بجميع بيانات هوية الموظفين
Employe[] GetAllIdentitesEmployes();
// موظف معين مع مستحقاته
Employe GetEmploye(string ss);
// قائمة بجميع الاشتراكات
Cotisations GetCotisations();
}
}
2.1.1. مشروع Visual Studio C# للطبقة [dao]
مشروع Visual Studio للطبقة [dao] هو التالي:
![]() |
- في [1]، المشروع بأكمله
- يحتوي المجلد [pam] على فئات المشروع بالإضافة إلى تكوين الكيانات NHibernate
- تقوم الملفات [App.config] و [Dao.xml] بتكوين إطار عمل Spring / NHibernate. سيتعين علينا وصف محتوى هذين الملفين.
- في [2]، الفئات المختلفة للمشروع
- في المجلد [entites] نجد الكيانات NHibernate التي تمت دراستها في المشروع السابق (انظر الصفحة 14)
- في المجلد [service]، نجد واجهة [IPamDao] وتنفيذها باستخدام إطار عمل Spring / NHibernate [PamDaoSpringNHibernate].
- يحتوي الملف [tests] على اختبارات واجهة [IPamDao].
- في [3]، مراجع المشروع. يتطلب تكامل Spring / NHibernate ملفات DLL و [4] جديدة.
في مراجع المشروع [3]، نجد المراجع التالية:
- NHibernate: لـ ORM NHibernate
- MySql.Data: برنامج التشغيل ADO.NET لـ SGBD MySQL 5
- Spring.Core: لإطار عمل Spring الذي يضمن تكامل الطبقات
- log4net: مكتبة سجلات
- nunit.framework: مكتبة للاختبارات الفردية
- Spring.Aop و Spring.Data و Spring.Data.NHibernate32: تضمن دعم Spring / NHibernate.
سنحرص على أن تكون خاصية "نسخة محلية" لجميع ملفات DLL هذه هي True.
2.1.2. تكوين مشروع C#
يتم تكوين المشروع على النحو التالي:
![]() |
- في [1]، اسم تجميع المشروع هو [pam-dao-spring-nhibernate]. يظهر هذا الاسم في ملفات تكوين المشروع المختلفة.
2.1.3. كيانات الطبقة [dao]
![]() |
تم تجميع الكيانات (الكائنات) اللازمة للطبقة [dao] في المجلد [entites] [1] الخاص بالمشروع. هذه الكيانات هي نفسها الموجودة في المشروع السابق (انظر الفقرة 1.3.2) مع اختلاف واحد موجود في ملفات التكوين NHibernate. لنأخذ على سبيل المثال الملف [Employe.hbm.xml]:
- في [2]، تم تكوين الملف ليتم تضمينه في تجميع المشروع
محتواه هو كما يلي:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Pam.Dao.Entites" assembly="pam-dao-spring-nhibernate">
<class name="Employe" table="EMPLOYES">
<id name="Id" column="ID">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="SS" column="SS" length="15" not-null="true" unique="true"/>
<property name="Nom" column="NOM" length="30" not-null="true"/>
<property name="Prenom" column="PRENOM" length="20" not-null="true"/>
<property name="Adresse" column="ADRESSE" length="50" not-null="true" />
<property name="Ville" column="VILLE" length="30" not-null="true"/>
<property name="CodePostal" column="CP" length="5" not-null="true"/>
<many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="all" lazy="false"/>
</class>
</hibernate-mapping>
- السطر 3: يشير السمة assembly إلى أن الملف [Employe.hbm.xml] سيتم العثور عليه في التجميع [pam-dao-spring-nhibernate]
بالإضافة إلى ذلك، في المجلد [entites]، نجد فئة استثناء يستخدمها المشروع:
using System;
namespace Pam.Dao.Entites {
public class PamException : Exception {
// رمز الخطأ
public int Code { get; set; }
// الشركات المصنعة
public PamException() {
}
public PamException(int Code)
: base() {
this.Code = Code;
}
public PamException(string message, int Code)
: base(message) {
this.Code = Code;
}
public PamException(string message, Exception ex, int Code)
: base(message, ex) {
this.Code = Code;
}
}
}
تم اشتقاق الفئة [PamException] من الفئة [Exception] (السطر 4) لإضافة رمز خطأ إليها (السطر 7).
2.1.4. تكوين Spring / NHibernate
لنعد إلى مشروع Visual C#:
![]() |
- في [1]، تقوم الملفات [App.config] و [Dao.xml] بتكوين تكامل Spring / NHibernate
2.1.4.1. الملف [App.config]
الملف [App.config] هو التالي:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- أقسام التكوين -->
<configSections>
<sectionGroup name="spring">
<section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
</sectionGroup>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<!-- تكوين Spring -->
<spring>
<parsers>
<parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
</parsers>
<context>
<resource uri="Dao.xml" />
</context>
</spring>
<!-- يحتوي هذا القسم على إعدادات تكوين log4net -->
<!-- NOTE IMPORTANTE: السجلات غير نشطة بشكل افتراضي. يجب تفعيلها برمجياً
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-5level %logger - %message%newline" />
</layout>
</appender>
<!-- اضبط مستوى التسجيل الافتراضي على DEBUG -->
<root>
<level value="DEBUG" />
<appender-ref ref="ConsoleAppender" />
</root>
<!-- تعيين التسجيل لـ Spring. تتوافق أسماء المسجلات في Spring مع مساحة الاسم -->
<logger name="Spring">
<level value="INFO" />
</logger>
<logger name="Spring.Data">
<level value="DEBUG" />
</logger>
<logger name="NHibernate">
<level value="DEBUG" />
</logger>
</log4net>
</configuration>
الملف [App.config] أعلاه يقوم بتكوين Spring (الأسطر 5-9، 15-22)، و log4net (السطر 10، الأسطر 28-53) ولكن ليس NHibernate. لا يتم تكوين كائنات Spring في [App.config] بل في الملف [Dao.xml] (السطر 20). وبالتالي، سيتم العثور على تكوين Spring / NHibernate الذي يتكون من إعلان كائنات Spring معينة في هذا الملف.
2.1.4.2. الملف [Dao.xml]
الملف [Dao.xml] الذي يجمع الكائنات التي يديرها Spring هو التالي:
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:db="http://www.springframework.net/database">
<!-- يُشار إليه بواسطة ملف تكوين سياق التطبيق الرئيسي -->
<description>
Application Spring / NHibernate
</description>
<!-- قاعدة البيانات وتكوين NHibernate -->
<db:provider id="DbProvider"
provider="MySql.Data.MySqlClient"
connectionString="Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;"/>
<object id="NHibernateSessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate32">
<property name="DbProvider" ref="DbProvider"/>
<property name="MappingAssemblies">
<list>
<value>pam-dao-spring-nhibernate</value>
</list>
</property>
<property name="HibernateProperties">
<dictionary>
<entry key="dialect" value="NHibernate.Dialect.MySQL5Dialect"/>
<entry key="hibernate.show_sql" value="false"/>
</dictionary>
</property>
<property name="ExposeTransactionAwareSessionFactory" value="true" />
</object>
<!-- مدير المعاملات -->
<object id="transactionManager"
type="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate32">
<property name="DbProvider" ref="DbProvider"/>
<property name="SessionFactory" ref="NHibernateSessionFactory"/>
</object>
<!-- قالب Hibernate -->
<object id="HibernateTemplate" type="Spring.Data.NHibernate.Generic.HibernateTemplate">
<property name="SessionFactory" ref="NHibernateSessionFactory" />
<property name="TemplateFlushMode" value="Auto" />
<property name="CacheQueries" value="true" />
</object>
<!-- كائنات الوصول إلى البيانات -->
<object id="pamdao" type="Pam.Dao.Service.PamDaoSpringNHibernate, pam-dao-spring-nhibernate" init-method="init" destroy-method="destroy">
<property name="HibernateTemplate" ref="HibernateTemplate"/>
</object>
</objects>
- تقوم الأسطر 11-13 بتكوين الاتصال بقاعدة البيانات [dbpam_nhibernate]. ويوجد فيها:
- المزود ADO.NET الضروري للاتصال، وهنا مزود SGBD MySQL. وهذا يعني وجود DLL و [Mysql.Data] في مراجع المشروع.
- سلسلة الاتصال بقاعدة البيانات (الخادم، اسم قاعدة البيانات، مالك الاتصال، كلمة المرور الخاصة به)
- تقوم الأسطر 15-29 بتكوين SessionFactory من NHibernate، وهو الكائن المستخدم للحصول على جلسات NHibernate. يُذكر أن أي عملية على قاعدة البيانات تتم داخل جلسة NHibernate. في السطر 15، يمكن ملاحظة أن SessionFactory يتم تنفيذه بواسطة فئة Spring Spring.Data.NHibernate.LocalSessionFactoryObject الموجودة في DLL Spring.Data.NHibernate32.
- السطر 16: تحدد الخاصية DbProvider معلمات الاتصال بقاعدة البيانات (المزود ADO.NET وسلسلة الاتصال). هنا تشير هذه الخاصية إلى الكائن DbProvider المحدد سابقًا في الأسطر 11-13.
- الأسطر 17-20: تحدد قائمة التجميعات التي تحتوي على ملفات [*.hbm.xml] التي تكوّن الكيانات التي يديرها NHibernate. تشير السطر 19 إلى أن هذه الملفات ستوجد في تجميع المشروع. نذكر أن هذا الاسم موجود في خصائص مشروع C#. نذكر أيضًا أن جميع ملفات [*.hbm.xml] قد تم تكوينها ليتم تضمينها في تجميع المشروع.
- الأسطر 22-27: خصائص محددة لـ NHibernate.
- السطر 24: ستكون اللهجة المستخدمة SQL هي تلك الخاصة بـ MySQL
- السطر 25: لن يظهر SQL الصادر عن NHibernate في سجلات وحدة التحكم. تعيين هذه الخاصية إلى true يسمح بمعرفة أوامر SQL الصادرة عن NHibernate. وهذا قد يساعد في فهم سبب بطء أحد التطبيقات عند الوصول إلى قاعدة البيانات، على سبيل المثال.
- السطر 28: ستؤدي الخاصية ExposeTransactionAwareSessionFactory إلى true إلى قيام Spring بإدارة تعليقات إدارة المعاملات التي سيتم العثور عليها في كود C#. سنعود إلى هذا الموضوع عندما نكتب الفئة التي تنفذ الطبقة [dao].
- تحدد الأسطر 32-36 مدير المعاملات. ومرة أخرى، هذا المدير هو فئة Spring من DLL Spring.Data.NHibernate32. يحتاج هذا المدير إلى معرفة معلمات الاتصال بقاعدة البيانات (السطر 34) وكذلك SessionFactory من NHibernate (السطر 35).
- تحدد الأسطر 39-43 خصائص فئة HibernateTemplate، وهي أيضًا فئة من Spring. ستُستخدم هذه الفئة كفئة مساعدة في الفئة التي تنفذ الطبقة [dao]. وهي تسهل التفاعلات مع كائنات NHibernate. تحتوي هذه الفئة على بعض الخصائص التي يجب تهيئتها:
- السطر 40: SessionFactory من NHibernate
- السطر 41: تحدد الخاصية TemplateFlushMode وضع مزامنة سياق الاستمرارية NHibernate مع قاعدة البيانات. يؤدي الوضع Auto إلى إجراء المزامنة:
- في نهاية المعاملة
- قبل إجراء عملية select
- السطر 42: سيتم تخزين استعلامات HQL (لغة استعلام Hibernate) في ذاكرة التخزين المؤقت. قد يؤدي ذلك إلى تحسين الأداء.
- الأسطر 46-48 تحدد فئة تنفيذ الطبقة [dao]
- السطر 46: سيتم تنفيذ الطبقة [dao] بواسطة الفئة [PamdaoSpringNHibernate] من DLL [pam-dao-spring-nhibernate]. بعد إنشاء مثيل للفئة، سيتم تنفيذ الطريقة init الخاصة بالفئة على الفور. عند إغلاق حاوية Spring، سيتم تنفيذ الطريقة destroy الخاصة بالفئة.
- السطر 47: ستحتوي الفئة [PamDaoSpringNHibernate] على خاصية HibernateTemplate التي سيتم تهيئتها باستخدام الخاصية HibernateTemplate الموجودة في السطر 39.
2.1.5. تنفيذ الطبقة [dao]
2.1.5.1. هيكل الفئة التنفيذية
الواجهة [IPamDao] هي كما يلي:
using Pam.Dao.Entites;
namespace Pam.Dao.Service {
public interface IPamDao {
// قائمة بجميع هويات الموظفين
Employe[] GetAllIdentitesEmployes();
// موظف معين مع مستحقاته
Employe GetEmploye(string ss);
// قائمة بجميع الاشتراكات
Cotisations GetCotisations();
}
}
- السطر 1: يتم استيراد مساحة أسماء كيانات الطبقة [dao].
- السطر 3: الطبقة [dao] موجودة في مساحة الاسم [Pam.Dao.Service]. يمكن إنشاء عناصر مساحة الاسم [Pam.Dao.Entites] في عدة نسخ. يتم إنشاء عناصر مساحة الاسم [Pam.Dao.Service] في نسخة واحدة (singleton). وهذا ما برر اختيار أسماء مساحات الأسماء.
- السطر 4: تسمى الواجهة [IPamDao]. وهي تُعرِّف ثلاث طرق:
- السطر 6، [GetAllIdentitesEmployes] تُرجع مصفوفة من الكائنات من النوع [Employe] التي تمثل قائمة المربيات في شكل مبسط (الاسم، الاسم الأول، SS).
- السطر 8، [GetEmploye] تُرجع كائن [Employe]: الموظف الذي يحمل رقم الضمان الاجتماعي الذي تم تمريره كمعلمة إلى الطريقة مع التعويضات المرتبطة بمؤشره.
- السطر 10، [GetCotisations] يُرجع الكائن [Cotisations] الذي يغلف معدلات الاشتراكات الاجتماعية المختلفة التي يجب خصمها من الراتب الإجمالي.
قد يكون الهيكل الأساسي لفئة تنفيذ هذه الواجهة مع دعم Spring / NHibernate كما يلي:
using System;
using System.Collections;
using System.Collections.Generic;
using Pam.Dao.Entites;
using Spring.Data.NHibernate.Generic.Support;
using Spring.Transaction.Interceptor;
namespace Pam.Dao.Service {
public class PamDaoSpringNHibernate : HibernateDaoSupport, IPamDao {
// الحقول الخاصة
private Cotisations cotisations;
private Employe[] employes;
// البدء
[Transaction(ReadOnly = true)]
public void init() {
...
}
// حذف الكائن
public void destroy() {
if (HibernateTemplate.SessionFactory != null) {
HibernateTemplate.SessionFactory.Close();
}
}
// قائمة بجميع هويات الموظفين
public Employe[] GetAllIdentitesEmployes() {
return employes;
}
// موظف معين مع مستحقاته
[Transaction(ReadOnly = true)]
public Employe GetEmploye(string ss) {
....
}
// قائمة الاشتراكات
public Cotisations GetCotisations() {
return cotisations;
}
}
}
- السطر 9: تنفذ الفئة [PamDaoSpringNHibernate] واجهة الطبقة [dao] [IPamDao] بشكل صحيح. كما أنها مشتقة من فئة Spring [HibernateDaoSupport]. تحتوي هذه الفئة على خاصية [HibernateTemplate] التي يتم تهيئتها بواسطة تكوين Spring الذي تم إجراؤه (السطر 2 أدناه):
<object id="pamdao" type="Pam.Dao.Service.PamDaoSpringNHibernate, pam-dao-spring-nhibernate" init-method="init" destroy-method="destroy">
<property name="HibernateTemplate" ref="HibernateTemplate"/>
</object>
- في السطر 1 أعلاه، نرى أن تعريف الكائن [pamdao] يشير إلى أن الطريقتين init و destroy من الفئة [PamDaoSpringNHibernate] يجب تنفيذهما في أوقات محددة. هاتان الطريقتان موجودتان بالفعل في الفئة في السطرين 16 و 21.
- السطران 15 و34: تعليقات توضيحية تجعل الطريقة المُعلَّقة تُنفَّذ ضمن معاملة. يشير السمة ReadOnly=true إلى أن المعاملة هي للقراءة فقط. يمكن للطريقة التي يتم تنفيذها في المعاملة أن تطلق استثناءً. في هذه الحالة، يقوم Spring بإجراء Rollback تلقائي للمعاملة. يزيل هذا التعليق الحاجة إلى إدارة معاملة داخل الطريقة.
- السطر 16: يتم تنفيذ الطريقة init بواسطة Spring فور إنشاء مثيل للفئة. سنرى أن الغرض منها هو تهيئة الحقول الخاصة في السطرين 11 و 12. وستتم هذه العملية ضمن معاملة (السطر 15).
- يتم تنفيذ طرق واجهة [IPamDao] في الأسطر 28 و35 و40.
- السطور 28-30: تقتصر الطريقة [GetAllIdentitesEmployes] على إرجاع السمة الموجودة في السطر 12 التي تم تهيئتها بواسطة الطريقة init.
- الأسطر 40-42: تقتصر طريقة [GetCotisations] على إرجاع السمة الموجودة في السطر 11 التي تم تهيئتها بواسطة الطريقة init.
2.1.5.2. الطرق المفيدة للفئة HibernateTemplate
سنستخدم الطرق التالية من الفئة HibernateTemplate:
تنفذ الاستعلام HQL وتُرجع قائمة من الكائنات من النوع T | |
تنفذ استعلام HQL الذي تم تعيين معلماته بواسطة ?. يتم توفير قيم هذه المعلمات من خلال جدول الكائنات. | |
يعرض جميع الكيانات من النوع T |
هناك طرق أخرى مفيدة لن تتاح لنا الفرصة لاستخدامها، والتي تسمح بالبحث عن الكيانات وحفظها وتحديثها وحذفها:
يضع في الجلسة NHibernate الكيان من النوع T الذي له المفتاح الأساسي id. | |
يدرج (INSERT) أو يحدّث (UPDATE) الكائن entité حسب ما إذا كان هذا الكائن يمتلك مفتاحًا أساسيًا (UPDATE) أم لا (INSERT). يمكن تكوين عدم وجود مفتاح أساسي بواسطة السمة unsaved-values في ملف تكوين الكيان. بعد العملية SaveOrUpdate، يكون الكائن entité موجودًا في الجلسة NHibernate. | |
يحذف الكائن entité من الجلسة NHibernate. |
2.1.5.3. تنفيذ طريقة init
الطريقة init للفئة [PamDaoSpringNHibernate] هي، حسب التكوين، الطريقة التي يتم تنفيذها بعد إنشاء مثيل للفئة بواسطة Spring. والغرض منها هو تخزين الهويات المبسطة للموظفين (الاسم، الاسم الأول، SS) ومعدلات الاشتراكات في ذاكرة التخزين المؤقتة المحلية. وقد يكون كودها كما يلي.
[Transaction(ReadOnly = true)]
public void init() {
try {
// استرداد القائمة المبسطة للموظفين
IList<object[]> lignes = HibernateTemplate.Find<object[]>("select e.SS,e.Nom,e.Prenom from Employe e");
// وضعها في جدول
employes = new Employe[lignes.Count];
int i = 0;
foreach (object[] ligne in lignes) {
employes[i] = new Employe() { SS = ligne[0].ToString(), Nom = ligne[1].ToString(), Prenom = ligne[2].ToString() };
i++;
}
// نضع معدلات الاشتراكات في كائن
cotisations = (HibernateTemplate.LoadAll<Cotisations>())[0];
} catch (Exception ex) {
// تحويل الاستثناء
throw new PamException(string.Format("Erreur d'accès à la BD : [{0}]", ex.ToString()), 43);
}
}
- السطر 5: يتم تنفيذ استعلام HQL. يطلب هذا الاستعلام الحقول SS، الاسم، الاسم الأول لجميع الكيانات Employé. ويعرض قائمة من الكائنات. إذا كنا قد طلبنا جميع بيانات الموظف بالشكل "select e from Employe e"، لكنا حصلنا على قائمة من الكائنات من النوع Employe.
- الأسطر 7-12: يتم نسخ قائمة الكائنات هذه إلى مصفوفة من الكائنات من النوع Employe.
- السطر 14: نطلب قائمة بجميع الكيانات من النوع Cotisations. نعلم أن هذه القائمة تحتوي على عنصر واحد فقط. لذلك نسترد العنصر الأول من القائمة للحصول على معدلات الاشتراكات.
- تقوم السطران 7 و14 بتهيئة الحقلين الخاصين بالفئة.
2.1.5.4. تنفيذ الطريقة GetEmploye
يجب أن تعرض الطريقة GetEmploye الكيان "الموظف" الذي يحمل الرقم SS المحدد. قد يكون كودها كما يلي:
[Transaction(ReadOnly = true)]
public Employe GetEmploye(string ss) {
IList<Employe> employés = null;
try {
// الاستعلام
employés = HibernateTemplate.Find<Employe>("select e from Employe e where e.SS=?", new object[]{ss});
} catch (Exception ex) {
// نقوم بتحويل الاستثناء
throw new PamException(string.Format("Erreur d'accès à la BD lors de la demande de l'employé de n° ss [{0}] : [{1}]", ss, ex.ToString()), 41);
}
// هل تم استرداد موظف؟
if (employés.Count == 0) {
// يتم الإبلاغ عن الأمر
throw new PamException(string.Format("L'employé de n° ss [{0}] n'existe pas", ss), 42);
} else {
return employés[0];
}
}
- السطر 6: يحصل على قائمة الموظفين الذين يحملون رقم SS المحدد
- السطر 12: عادةً، إذا كان الموظف موجودًا، يجب الحصول على قائمة تحتوي على عنصر واحد
- السطر 14: إذا لم يكن الأمر كذلك، يتم إصدار استثناء
- السطر 16: إذا كان الأمر كذلك، يتم إرجاع أول موظف في القائمة
2.2. اختبارات الطبقة [dao]
2.2.1. مشروع Visual Studio
تم عرض مشروع Visual Studio سابقًا. وللتذكير:
![]() |
- في [1]، المشروع بأكمله
- في [2]، الفئات المختلفة للمشروع. يحتوي المجلد [tests] على اختبار وحدة التحكم [Main.cs] واختبار وحدة [NUnit.cs].
- في [3]، يتم ترجمة البرنامج [Main.cs].
![]() |
- في [4]، لم يتم إنشاء الملف [NUnit.cs].
- المشروع هو تطبيق وحدة التحكم. الفئة المنفذة هي تلك المحددة في [5]، وهي فئة الملف [Main.cs].
2.2.2. برنامج اختبار وحدة التحكم [Main.cs]
يتم تنفيذ برنامج الاختبار [Main.cs] في البنية التالية:
![]() |
وهو مسؤول عن اختبار أساليب واجهة [IPamDao]. قد يكون المثال الأساسي كما يلي:
using System;
using Pam.Dao.Entites;
using Pam.Dao.Service;
using Spring.Context.Support;
namespace Pam.Dao.Tests {
public class MainPamDaoTests {
public static void Main() {
try {
// إنشاء مثيل الطبقة [dao]
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
// قائمة بهويات الموظفين
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes()) {
Console.WriteLine(Employe.ToString());
}
// موظف مع مستحقاته
Console.WriteLine("------------------------------------");
Console.WriteLine(pamDao.GetEmploye("254104940426058"));
Console.WriteLine("------------------------------------");
// قائمة الاشتراكات
Cotisations cotisations = pamDao.GetCotisations();
Console.WriteLine(cotisations.ToString());
} catch (Exception ex) {
// عرض الاستثناء
Console.WriteLine(ex.ToString());
}
//استراحة
Console.ReadLine();
}
}
}
- السطر 11: يُطلب من Spring مرجعًا على الطبقة [dao].
- الأسطر 13-15: اختبار الطريقة [GetAllIdentitesEmployes] للواجهة [IPamDao]
- السطر 18: اختبار الطريقة [GetEmploye] للواجهة [IPamDao]
- السطر 21: اختبار الطريقة [GetCotisations] للواجهة [IPamDao]
يتم تكوين Spring و NHibernate و log4net بواسطة الملف [App.config] الذي تمت دراسته في الفقرة 2.1.4.1.
يؤدي التنفيذ باستخدام قاعدة البيانات الموصوفة في الفقرة 1.2 إلى الحصول على النتيجة التالية في وحدة التحكم:
- السطران 1-2: الموظفان من النوع [Employe] مع المعلومات التالية فقط [SS, Nom, Prenom]
- السطر 4: الموظف من النوع [Employe] الذي يحمل رقم الضمان الاجتماعي [254104940426058]
- السطر 5: معدلات الاشتراكات
2.2.3. اختبارات وحدة مع NUnit
ننتقل الآن إلى اختبار فردي لـ NUnit. سيتطور مشروع Visual Studio للطبقة [dao] على النحو التالي:
![]() |
- إلى [1]، وبرنامج الاختبار [NUnit.cs]
- إلى [2,3]، وسيُنشئ المشروع ملف DLL باسم [pam-dao-spring-nhibernate.dll]
- في [4]، الإشارة إلى DLL من إطار العمل NUnit: [nunit.framework.dll]
- إلى [5]، لن يتم تضمين الفئة [Main.cs] في DLL [pam-dao-spring-nhibernate]
- في [6]، سيتم تضمين الفئة [NUnit.cs] في DLL [pam-dao-spring-nhibernate]
فئة الاختبار NUnit هي كما يلي:
using System.Collections;
using NUnit.Framework;
using Pam.Dao.Service;
using Pam.Dao.Entites;
using Spring.Objects.Factory.Xml;
using Spring.Core.IO;
using Spring.Context.Support;
namespace Pam.Dao.Tests {
[TestFixture]
public class NunitPamDao : AssertionHelper {
// الطبقة [dao] المراد اختبارها
private IPamDao pamDao = null;
// المنشئ
public NunitPamDao() {
// إنشاء مثيل الطبقة [dao]
pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
}
// التشغيل
[SetUp]
public void Init() {
}
[Test]
public void GetAllIdentitesEmployes() {
// التحقق من عدد الموظفين
Expect(2, EqualTo(pamDao.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetCotisations() {
// التحقق من معدل الاشتراكات
Cotisations cotisations = pamDao.GetCotisations();
Expect(3.49, EqualTo(cotisations.CsgRds).Within(1E-06));
Expect(6.15, EqualTo(cotisations.Csgd).Within(1E-06));
Expect(9.39, EqualTo(cotisations.Secu).Within(1E-06));
Expect(7.88, EqualTo(cotisations.Retraite).Within(1E-06));
}
[Test]
public void GetEmployeIdemnites() {
// التحقق من الأفراد
Employe employe1 = pamDao.GetEmploye("254104940426058");
Employe employe2 = pamDao.GetEmploye("260124402111742");
Expect("Jouveinal", EqualTo(employe1.Nom));
Expect(2.1, EqualTo(employe1.Indemnites.BaseHeure).Within(1E-06));
Expect("Laverti", EqualTo(employe2.Nom));
Expect(1.93, EqualTo(employe2.Indemnites.BaseHeure).Within(1E-06));
}
[Test]
public void GetEmployeIdemnites2() {
// التحقق من عدم وجود الفرد
bool erreur = false;
try {
Employe employe1 = pamDao.GetEmploye("xx");
} catch {
erreur = true;
}
Expect(erreur, True);
}
}
}
- السطر 11: تحتوي الفئة على السمة [TestFixture] التي تجعلها فئة اختبار [NUnit].
- السطر 12: الفئة مشتقة من الفئة المساعدة AssertionHelper في إطار العمل NUnit (بدءًا من الإصدار 2.4.6).
- السطر 14: الحقل الخاص [pamDao] هو مثيل لواجهة الوصول إلى الطبقة [dao]. تجدر الإشارة إلى أن نوع هذا الحقل هو واجهة وليس فئة. وهذا يعني أن مثيل [pamDao] لا يتيح سوى الوصول إلى الطرق، وهي طرق واجهة [IPamDao].
- الطرق التي يتم اختبارها في الفئة هي تلك التي لها السمة [Test]. بالنسبة لجميع هذه الطرق، تكون عملية الاختبار كما يلي:
- يتم أولاً تنفيذ الطريقة التي تحمل السمة [SetUp]. وهي تُستخدم لإعداد الموارد (اتصالات الشبكة، اتصالات قواعد البيانات، ...) اللازمة للاختبار.
- ثم يتم تنفيذ الطريقة المراد اختبارها
- وأخيرًا يتم تنفيذ الطريقة التي تحمل السمة [TearDown]. وهي تُستخدم عادةً لتحرير الموارد التي استخدمتها الطريقة ذات السمة [SetUp].
- في اختبارنا، لا توجد موارد يجب تخصيصها قبل كل اختبار ثم إلغاء تخصيصها بعد ذلك. لذلك، لا نحتاج إلى طريقة ذات السمتين [SetUp] و [TearDown]. في المثال الذي قدمناه، في الأسطر 23-26، عرضنا طريقة ذات السمة [SetUp].
- الأسطر 17-20: يقوم منشئ الفئة بتهيئة الحقل الخاص [pamDao] باستخدام Spring و [App.config].
- الأسطر 29-32: تختبر الطريقة [GetAllIdentitesEmployes]
- الأسطر 35-42: تختبر الطريقة [GetCotisations]
- الأسطر 45-53: تختبر الطريقة [GetEmploye]
- الأسطر 56-65: تختبر الطريقة [GetEmploye] عند حدوث استثناء.
يؤدي إنشاء المشروع إلى إنشاء DLL [pam-dao-spring-nhibernate.dll] في المجلد [bin/Release]:
![]() |
يتم تحميل ملف DLL و [pam-dao-spring-nhibernate.dll] باستخدام أداة [NUnit-Gui]، الإصدار 2.5، ثم يتم إجراء الاختبارات:
![]() |
فيما سبق، نجحت الاختبارات.
2.2.4. إنشاء و DLL من الطبقة [dao]
بمجرد كتابة فئة [PamDaoNHibernate] واختبارها، سنقوم بإنشاء DLL من الطبقة [dao] بالطريقة التالية:
![]() |
- [1]، يتم استبعاد برامج الاختبار من تجميع المشروع
- [2,3]، تكوين المشروع
- [4]، إنشاء المشروع
- يتم إنشاء DLL في المجلد [bin/Release] [5].












