Skip to content

2. Integracja Spring / NHibernate

Framework Spring oferuje klasy pomocnicze do pracy z frameworkiem NHibernate. Wykorzystanie tych klas ułatwia pisanie kodu służącego do uzyskiwania dostępu do danych z SGBD. Rozważmy następującą architekturę wielowarstwową:

W dalszej części stworzymy warstwę [dao] z wykorzystaniem [Spring / NHibernate], omawiając kod działającego rozwiązania. Nie będziemy starać się przedstawić wszystkich możliwości konfiguracji ani zastosowań frameworka [Spring / Nhibernate]. Czytelnik będzie mógł dostosować proponowane rozwiązanie do własnych potrzeb, korzystając z dokumentacji Spring.NET [Spring.NET | Homepage ] (grudzień 2011 r.).

2.1. Warstwa dostępu do danych [dao]

Bazą danych jest baza MySQL [dbpam_nhibernate], przedstawiona już w punkcie 1.2. Warstwa [dao] implementuje następujący interfejs w języku C#:


using Pam.Dao.Entites;

namespace Pam.Dao.Service {
    public interface IPamDao {
        // lista wszystkich numerów identyfikacyjnych pracowników 
        Employe[] GetAllIdentitesEmployes();
        // dany pracownik wraz z przysługującymi mu świadczeniami 
        Employe GetEmploye(string ss);
        // lista wszystkich składek 
        Cotisations GetCotisations();
    }
}

2.1.1. Projekt Visual Studio w języku C# dla warstwy [dao]

Projekt Visual Studio warstwy [dao] wygląda następująco:

  • w [1] znajduje się cały projekt
    • folder [pam] zawiera klasy projektu oraz konfigurację encji NHibernate
    • pliki [App.config] i [Dao.xml] konfigurują framework Spring / NHibernate. Będziemy musieli opisać zawartość tych dwóch plików.
  • W pliku [2] znajdują się różne klasy projektu
    • w folderze [entites] znajdują się encje NHibernate omówione w poprzednim projekcie (patrz strona 14)
    • w folderze [service] znajdujemy interfejs [IPamDao] oraz jego implementację przy użyciu frameworka Spring / NHibernate [PamDaoSpringNHibernate].
    • Folder [tests] zawiera testy interfejsu [IPamDao].
  • W pliku [3] znajdują się referencje projektu. Integracja Spring / NHibernate wymaga nowych plików DLL i [4].

W odwołaniach projektu [3] znajdują się następujące elementy DLL:

  • NHibernate: dla ORM i NHibernate
  • MySql.Data: sterownik ADO.NET dla SGBD MySQL 5
  • Spring.Core: dla frameworka Spring, który zapewnia integrację warstw
  • log4net: biblioteka logów
  • nunit.framework: biblioteka testów jednostkowych
  • Spring.Aop, Spring.Data i Spring.Data.NHibernate32: zapewniają obsługę Spring / NHibernate.

Należy zadbać o to, aby wszystkie pliki DLL miały właściwość „Kopia lokalna” ustawioną na True.

2.1.2. Konfiguracja projektu C#

Projekt jest skonfigurowany w następujący sposób:

  • w [1], nazwa zestawu projektu to [pam-dao-spring-nhibernate]. Nazwa ta pojawia się w różnych plikach konfiguracyjnych projektu.

2.1.3. Elementy warstwy [dao]

Elementy (obiekty) niezbędne dla warstwy [dao] zostały zebrane w folderze [entites] [1] projektu. Są to te same elementy, co w poprzednim projekcie (patrz punkt 1.3.2), z tą różnicą, że znajdują się one w plikach konfiguracyjnych NHibernate. Weźmy na przykład plik [Employe.hbm.xml]:

  • w pliku [2] plik ten jest skonfigurowany tak, aby został włączony do zestawu projektu

Jego zawartość jest następująca:


<?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>
  • wiersz 3: atrybut assembly wskazuje, że plik [Employe.hbm.xml] będzie znajdował się w zestawie [pam-dao-spring-nhibernate]

Ponadto w folderze [entites] znajduje się klasa wyjątków wykorzystywana przez projekt:


using System;
namespace Pam.Dao.Entites {

    public class PamException : Exception {

        // kod błędu 
        public int Code { get; set; }

        // producenci 
        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;
        }
    }
}

Klasa [PamException] została wywodzona z klasy [Exception] (wiersz 4) w celu dodania do niej kodu błędu (wiersz 7).

2.1.4. Konfiguracja Spring / NHibernate

Wróćmy do projektu Visual C#:

  • W pliku [1] pliki [App.config] i [Dao.xml] konfigurują integrację Spring / NHibernate

2.1.4.1. Plik [App.config]

Plik [App.config] ma następującą treść:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <!-- sekcje konfiguracyjne -->
    <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>


    <!-- konfiguracja Spring -->
    <spring>
        <parsers>
            <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
        </parsers>
        <context>
            <resource uri="Dao.xml" />
        </context>
    </spring>

    <!-- Ta sekcja zawiera ustawienia konfiguracyjne log4net -->
    <!-- NOTE IMPORTANTE: logi nie są domyślnie aktywne. Należy je aktywować programowo
    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>

        <!-- Ustaw domyślny poziom logowania na DEBUG -->
        <root>
            <level value="DEBUG" />
            <appender-ref ref="ConsoleAppender" />
        </root>

        <!-- Ustaw rejestrowanie dla Spring. Nazwy rejestratorów w Spring odpowiadają przestrzeni nazw -->
        <logger name="Spring">
            <level value="INFO" />
        </logger>

        <logger name="Spring.Data">
            <level value="DEBUG" />
        </logger>

        <logger name="NHibernate">
            <level value="DEBUG" />
        </logger>
    </log4net>

</configuration>

Powyższy plik [App.config] konfiguruje Spring (wiersze 5–9, 15–22), log4net (wiersz 10, wiersze 28–53), ale nie NHibernate. Obiekty Springa nie są skonfigurowane w pliku [App.config], lecz w pliku [Dao.xml] (wiersz 20). Konfiguracja Springa / NHibernate, polegająca na zadeklarowaniu konkretnych obiektów Springa, znajduje się zatem w tym pliku.

2.1.4.2. Plik [Dao.xml]

Plik [Dao.xml], zawierający obiekty zarządzane przez Spring, wygląda następująco:


<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
         xmlns:db="http://www.springframework.net/database">

    <!-- Odwołuje się do głównego pliku konfiguracyjnego kontekstu aplikacji -->
    <description>
        Application Spring / NHibernate
    </description>

    <!-- Konfiguracja bazy danych i 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>

    <!-- menedżer transakcji -->
    <object id="transactionManager"
        type="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate32">
        <property name="DbProvider" ref="DbProvider"/>
        <property name="SessionFactory" ref="NHibernateSessionFactory"/>
    </object>

    <!-- Szablon 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>

    <!-- Obiekty dostępu do danych -->
    <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>
  • Wiersze 11–13 konfigurują połączenie z bazą danych [dbpam_nhibernate]. Znajduje się tam:
    • dostawca ADO.NET niezbędny do połączenia, w tym przypadku dostawca z pliku SGBD MySQL. Oznacza to, że w odwołaniach projektu muszą znajdować się dostawcy DLL i [Mysql.Data].
    • łańcuch połączenia z bazą danych (serwer, nazwa bazy, właściciel połączenia, jego hasło)
  • wiersze 15–29 konfigurują obiekt SessionFactory z NHibernate, który służy do uzyskiwania sesji NHibernate. Przypominamy, że każda operacja na bazie danych odbywa się w ramach sesji NHibernate. W wierszu 15 widać, że SessionFactory jest zaimplementowany przez klasę Spring Spring.Data.NHibernate.LocalSessionFactoryObject znajdującą się w DLL Spring.Data.NHibernate32.
  • Wiersz 16: właściwość DbProvider określa parametry połączenia z bazą danych (dostawca ADO.NET i ciąg połączenia). W tym przypadku właściwość ta odwołuje się do obiektu DbProvider zdefiniowanego wcześniej w wierszach 11–13.
  • wiersze 17–20: określają listę zestawów zawierających pliki [*.hbm.xml] konfigurujące elementy zarządzane przez NHibernate. Wiersz 19 wskazuje, że pliki te będą znajdować się w zestawie projektu. Przypominamy, że nazwa ta znajduje się we właściwościach projektu C#. Przypominamy również, że wszystkie pliki [*.hbm.xml] zostały skonfigurowane tak, aby były włączone do zestawu projektu.
  • wiersze 22–27: właściwości specyficzne dla NHibernate.
    • wiersz 24: używany dialekt SQL będzie pochodził z MySQL
    • wiersz 25: plik SQL wygenerowany przez NHibernate nie pojawi się w logach konsoli. Ustawienie tej właściwości na true pozwala poznać polecenia SQL wysłane przez NHibernate. Może to pomóc na przykład w zrozumieniu, dlaczego aplikacja działa wolno podczas dostępu do bazy danych.
  • wiersz 28: ustawienie właściwości ExposeTransactionAwareSessionFactory na true spowoduje, że Spring będzie zarządzał adnotacjami dotyczącymi transakcji, które zostaną znalezione w kodzie C#. Powrócimy do tego tematu podczas pisania klasy implementującej warstwę [dao].
  • Wiersze 32–36 definiują menedżera transakcji. Również w tym przypadku menedżer ten jest klasą Springa o nazwie DLL Spring.Data.NHibernate32. Menedżer ten musi znać parametry połączenia z bazą danych (wiersz 34), a także SessionFactory z NHibernate (wiersz 35).
  • Wiersze 39–43 definiują właściwości klasy HibernateTemplate, która również jest klasą Springa. Klasa ta będzie wykorzystywana jako klasa pomocnicza w klasie implementującej warstwę [dao]. Ułatwia ona interakcje z obiektami NHibernate. Klasa ta posiada pewne właściwości, które należy zainicjować:
    • wiersz 40: SessionFactory z klasy NHibernate
    • wiersz 41: właściwość TemplateFlushMode określa tryb synchronizacji kontekstu trwałości NHibernate z bazą danych. Tryb Auto powoduje, że synchronizacja nastąpi:
      • na końcu transakcji
      • przed operacją select
    • wiersz 42: zapytania HQL (Hibernate Query Language) będą buforowane. Może to przyczynić się do poprawy wydajności.
  • wiersze 46–48 definiują klasę implementacyjną warstwy [dao]
    • wiersz 46: warstwa [dao] zostanie zaimplementowana przez klasę [PamdaoSpringNHibernate] z klasy DLL [pam-dao-spring-nhibernate]. Po utworzeniu instancji klasy natychmiast zostanie wykonana metoda init tej klasy. Po zamknięciu kontenera Spring zostanie wykonana metoda destroy tej klasy.
    • wiersz 47: klasa [PamDaoSpringNHibernate] będzie posiadać właściwość HibernateTemplate, która zostanie zainicjowana wartością właściwości HibernateTemplate z wiersza 39.

2.1.5. Implementacja warstwy [dao]

2.1.5.1. Szkielet klasy implementacyjnej

Interfejs [IPamDao] ma następujący wygląd:


using Pam.Dao.Entites;

namespace Pam.Dao.Service {
    public interface IPamDao {
        // lista wszystkich tożsamości pracowników 
        Employe[] GetAllIdentitesEmployes();
        // konkretny pracownik wraz z jego dodatkami 
        Employe GetEmploye(string ss);
        // lista wszystkich składek 
        Cotisations GetCotisations();
    }
}
  • wiersz 1: importujemy przestrzeń nazw encji warstwy [dao].
  • wiersz 3: warstwa [dao] znajduje się w przestrzeni nazw [Pam.Dao.Service]. Elementy przestrzeni nazw [Pam.Dao.Entites] mogą być tworzone w wielu egzemplarzach. Elementy przestrzeni nazw [Pam.Dao.Service] są tworzone w jednym egzemplarzu (singleton). To właśnie uzasadniało wybór nazw przestrzeni nazw.
  • wiersz 4: interfejs nosi nazwę [IPamDao]. Definiuje on trzy metody:
    • w wierszu 6 metoda [GetAllIdentitesEmployes] zwraca tablicę obiektów typu [Employe], która przedstawia listę opiekunek do dzieci w uproszczonej formie (nazwisko, imię, SS).
    • w wierszu 8 metoda [GetEmploye] zwraca obiekt typu [Employe]: pracownika o numerze ubezpieczenia społecznego przekazanym jako parametr metody wraz z dodatkami związanymi z jego wskaźnikiem.
    • w wierszu 10, [GetCotisations] zwraca obiekt [Cotisations], który zawiera stawki różnych składek na ubezpieczenie społeczne pobieranych od wynagrodzenia brutto.

Szkielet klasy implementującej ten interfejs z wykorzystaniem Spring / NHibernate mógłby wyglądać następująco:


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 {
        // pola prywatne 
        private Cotisations cotisations;
        private Employe[] employes;

        // init 
        [Transaction(ReadOnly = true)]
        public void init() {
...
        }

        // usunięcie obiektu
        public void destroy() {
            if (HibernateTemplate.SessionFactory != null) {
                HibernateTemplate.SessionFactory.Close();
            }
        }

        // lista wszystkich tożsamości pracowników
        public Employe[] GetAllIdentitesEmployes() {
            return employes;
        }


        // konkretny pracownik wraz z jego dodatkami 
        [Transaction(ReadOnly = true)]
        public Employe GetEmploye(string ss) {
....
        }

        // lista składek 
        public Cotisations GetCotisations() {
            return cotisations;
        }
    }
}
  • wiersz 9: klasa [PamDaoSpringNHibernate] prawidłowo implementuje interfejs warstwy [dao] [IPamDao]. Wywodzi się ona również z klasy Spring [HibernateDaoSupport]. Klasa ta posiada właściwość [HibernateTemplate], która jest inicjowana przez skonfigurowane ustawienia Springa (wiersz 2 poniżej):

    <object id="pamdao" type="Pam.Dao.Service.PamDaoSpringNHibernate, pam-dao-spring-nhibernate" init-method="init" destroy-method="destroy">
        <property name="HibernateTemplate" ref="HibernateTemplate"/>
</object>
  • W wierszu 1 powyżej widać, że definicja obiektu [pamdao] wskazuje, iż metody init i destroy klasy [PamDaoSpringNHibernate] muszą zostać wykonane w określonych momentach. Obie te metody rzeczywiście znajdują się w tej klasie w wierszach 16 i 21.
  • wiersze 15, 34: adnotacje, które powodują, że metoda opatrzona adnotacją zostanie wykonana w ramach transakcji. Atrybut ReadOnly=true oznacza, że transakcja ma charakter tylko do odczytu. Metoda wykonywana w ramach transakcji może wygenerować wyjątek. W takim przypadku Spring automatycznie Rollback transakcji. Adnotacja ta eliminuje konieczność zarządzania transakcją wewnątrz metody.
  • wiersz 16: metoda init jest wykonywana przez Spring natychmiast po instancjonowaniu klasy. Zobaczymy, że jej celem jest zainicjowanie pól prywatnych z wierszy 11 i 12. Metoda ta będzie przebiegać w ramach transakcji (wiersz 15).
  • Metody interfejsu [IPamDao] są zaimplementowane w wierszach 28, 35 i 40.
  • wiersze 28–30: metoda [GetAllIdentitesEmployes] ogranicza się do zwracania atrybutu z wiersza 12 zainicjowanego przez metodę init.
  • wiersze 40–42: metoda [GetCotisations] ogranicza się do zwracania atrybutu z wiersza 11 zainicjowanego przez metodę init.

2.1.5.2. Przydatne metody klasy HibernateTemplate

Będziemy korzystać z następujących metod klasy HibernateTemplate:

IList<T> Find<T>(string requete_hql)
wykonuje zapytanie HQL i zwraca listę obiektów typu T
IList<T> Find<T>(string requete_hql, object[])
wykonuje zapytanie HQL z parametrami oznaczonymi jako ?. Wartości tych parametrów są dostarczane przez tablicę obiektów.
IList<T> LoadAll<T>()
zwraca wszystkie encje typu T

Istnieją inne przydatne metody, których nie będziemy mieli okazji wykorzystać, a które pozwalają na wyszukiwanie, zapisywanie, aktualizowanie i usuwanie elementów:

T Load<T>(object id)
umieszcza w sesji NHibernate encję typu T o kluczu głównym id.
void SaveOrUpdate(object entité)
wstawia (INSERT) lub aktualizuje (UPDATE) obiekt entité w zależności od tego, czy posiada on klucz główny (UPDATE), czy nie (INSERT). Brak klucza głównego można skonfigurować za pomocą atrybutu unsaved-values w pliku konfiguracyjnym jednostki. Po operacji SaveOrUpdate obiekt entité znajduje się w sesji NHibernate.
void Delete(object entité)
usuwa obiekt entité z sesji NHibernate.

2.1.5.3. Implementacja metody init

Metoda init klasy [PamDaoSpringNHibernate] jest z założenia metodą wykonywaną po utworzeniu instancji klasy przez Spring. Jej celem jest zapisanie w lokalnej pamięci podręcznej uproszczonych danych identyfikacyjnych pracowników (nazwisko, imię, SS) oraz stawek składek. Jej kod mógłby wyglądać następująco.


[Transaction(ReadOnly = true)]
        public void init() {
            try {
                // pobieramy uproszczoną listę pracowników
                IList<object[]> lignes = HibernateTemplate.Find<object[]>("select e.SS,e.Nom,e.Prenom from Employe e");
                // umieszczamy ją w tabeli
                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++;
                }
                // umieszczamy stawki składek w obiekcie 
                cotisations = (HibernateTemplate.LoadAll<Cotisations>())[0];
            } catch (Exception ex) {
                // przetwarzamy wyjątek 
                throw new PamException(string.Format("Erreur d'accès à la BD : [{0}]", ex.ToString()), 43);
            }
        }
  • wiersz 5: wykonywane jest zapytanie HQL. Żąda ono pól SS, Nazwisko, Imię wszystkich encji Employé. Zwraca listę obiektów. Gdybyśmy zapytali o pełne dane pracownika w postaci „select e from Employe e”, otrzymalibyśmy listę obiektów typu Employe.
  • wiersze 7–12: ta lista obiektów jest kopiowana do tablicy obiektów typu Employe.
  • wiersz 14: żądamy listy wszystkich encji typu Cotisations. Wiemy, że lista ta zawiera tylko jeden element. Pobieramy zatem pierwszy element listy, aby uzyskać stawki składek.
  • Wiersze 7 i 14 inicjują dwa pola prywatne klasy.

2.1.5.4. Implementacja metody GetEmploye

Metoda GetEmploye musi zwrócić jednostkę typu „Pracownik” o podanym numerze SS. Jej kod mógłby wyglądać następująco:


[Transaction(ReadOnly = true)]
        public Employe GetEmploye(string ss) {
            IList<Employe> employés = null;
            try {
                // zapytanie
                employés = HibernateTemplate.Find<Employe>("select e from Employe e where e.SS=?", new object[]{ss});
            } catch (Exception ex) {
                // przekształcamy wyjątek 
                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);
            }
            // czy udało się pobrać dane pracownika? 
            if (employés.Count == 0) {
                // zgłaszamy ten fakt 
                throw new PamException(string.Format("L'employé de n° ss [{0}] n'existe pas", ss), 42);
            } else {
                return employés[0];
            }
        }
  • wiersz 6: pobiera listę pracowników o podanym numerze SS
  • wiersz 12: zazwyczaj, jeśli pracownik istnieje, powinna pojawić się lista zawierająca jeden element
  • wiersz 14: jeśli tak nie jest, zgłaszany jest wyjątek
  • wiersz 16: jeśli tak jest, zwracamy pierwszego pracownika z listy

2.2. Testy warstwy [dao]

2.2.1. Projekt Visual Studio

Projekt Visual Studio został już przedstawiony. Przypomnijmy:

  • w [1], cały projekt
  • w [2] – poszczególne klasy projektu. Folder [tests] zawiera test konsolowy [Main.cs] oraz test jednostkowy [NUnit.cs].
  • w pliku [3] kompilowany jest program [Main.cs].
  • w pliku [4] plik [NUnit.cs] nie jest generowany.
  • Projekt jest aplikacją konsolową. Klasą wykonywaną jest ta określona w pliku [5], czyli klasa z pliku [Main.cs].

2.2.2. Program testowy konsolowy [Main.cs]

Program testowy [Main.cs] jest uruchamiany w następującej architekturze:

Jego zadaniem jest testowanie metod interfejsu [IPamDao]. Podstawowy przykład mógłby wyglądać następująco:


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 {
                // instancja warstwy [dao]
                IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
                // lista tożsamości pracowników 
                foreach (Employe Employe in pamDao.GetAllIdentitesEmployes()) {
                    Console.WriteLine(Employe.ToString());
                }
                // pracownik wraz z jego dodatkami 
                Console.WriteLine("------------------------------------");
                Console.WriteLine(pamDao.GetEmploye("254104940426058"));
                Console.WriteLine("------------------------------------");
                // lista składek 
                Cotisations cotisations = pamDao.GetCotisations();
                Console.WriteLine(cotisations.ToString());
            } catch (Exception ex) {
                // wyświetlenie wyjątku 
                Console.WriteLine(ex.ToString());
            }
            //przerwa 
            Console.ReadLine();
        }
    }
}
  • wiersz 11: zwracamy się do Springa o odwołanie do warstwy [dao].
  • wiersze 13–15: test metody [GetAllIdentitesEmployes] interfejsu [IPamDao]
  • wiersz 18: test metody [GetEmploye] z interfejsu [IPamDao]
  • wiersz 21: test metody [GetCotisations] z interfejsu [IPamDao]

Spring, NHibernate i log4net są konfigurowane przez plik [App.config] omówiony w paragrafie 2.1.4.1.

Uruchomienie programu z wykorzystaniem bazy danych opisanej w punkcie 1.2 daje następujący wynik na konsoli:

1
2
3
4
5
6
[254104940426058,Jouveinal,Marie,,,,]
[260124402111742,Laverti,Justine,,,,]
------------------------------------
[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203,[2, 2,1, 2,1, 3,1, 15]]
------------------------------------
[3,49,6,15,9,39,7,88]
  • wiersze 1–2: dwaj pracownicy typu [Employe], zawierające wyłącznie informacje z pliku [SS, Nom, Prenom]
  • wiersz 4: pracownik typu [Employe] o numerze ubezpieczenia społecznego [254104940426058]
  • wiersz 5: stawki składek

2.2.3. Testy jednostkowe z NUnit

Przechodzimy teraz do testu jednostkowego NUnit. Projekt Visual Studio warstwy [dao] ulegnie następującym zmianom:

  • do [1], program testowy [NUnit.cs]
  • na [2,3], projekt wygeneruje plik DLL o nazwie [pam-dao-spring-nhibernate.dll]
  • w [4], odwołanie do DLL z frameworka NUnit: [nunit.framework.dll]
  • w [5], klasa [Main.cs] nie zostanie uwzględniona w DLL [pam-dao-spring-nhibernate]
  • do [6], klasa [NUnit.cs] zostanie uwzględniona w klasie DLL [pam-dao-spring-nhibernate]

Klasa testowa NUnit ma następujący wygląd:


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 {
        // warstwa [dao] do przetestowania 
        private IPamDao pamDao = null;

        // konstruktor 
        public NunitPamDao() {
            // instancja warstwy [dao]
            pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
        }

        // inicjalizacja 
        [SetUp]
        public void Init() {

        }

        [Test]
        public void GetAllIdentitesEmployes() {
            // weryfikacja liczby pracowników 
            Expect(2, EqualTo(pamDao.GetAllIdentitesEmployes().Length));
        }

        [Test]
        public void GetCotisations() {
            // weryfikacja stawek składek 
            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() {
            // weryfikacja osób fizycznych 
            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() {
            // weryfikacja nieistniejącej osoby 
            bool erreur = false;
            try {
                Employe employe1 = pamDao.GetEmploye("xx");
            } catch {
                erreur = true;
            }
            Expect(erreur, True);
        }
    }
}
  • wiersz 11: klasa posiada atrybut [TestFixture], co czyni ją klasą testową [NUnit].
  • wiersz 12: klasa ta wywodzi się z klasy pomocniczej AssertionHelper z frameworka NUnit (od wersji 2.4.6).
  • wiersz 14: pole prywatne [pamDao] jest instancją interfejsu dostępu do warstwy [dao]. Należy zauważyć, że typem tego pola jest interfejs, a nie klasa. Oznacza to, że instancja [pamDao] udostępnia wyłącznie metody należące do interfejsu [IPamDao].
  • Metody testowane w tej klasie to te, które posiadają atrybut [Test]. W przypadku wszystkich tych metod proces testowania przebiega następująco:
    • najpierw wykonywana jest metoda posiadająca atrybut [SetUp]. Służy ona do przygotowania zasobów (połączeń sieciowych, połączeń z bazami danych itp.) niezbędnych do przeprowadzenia testu.
    • Następnie uruchamiana jest metoda, która ma zostać przetestowana
    • i na koniec wykonywana jest metoda o atrybucie [TearDown]. Służy ona zazwyczaj do zwolnienia zasobów wykorzystanych przez metodę o atrybucie [SetUp].
  • W naszym teście nie ma zasobów, które należałoby przydzielać przed każdym testem, a następnie zwalniać. Dlatego nie potrzebujemy metod z atrybutami [SetUp] i [TearDown]. W ramach przykładu w wierszach 23–26 przedstawiliśmy metodę z atrybutem [SetUp].
  • wiersze 17–20: konstruktor klasy inicjuje pole prywatne [pamDao] przy użyciu Springa i [App.config].
  • wiersze 29–32: testują metodę [GetAllIdentitesEmployes]
  • wiersze 35–42: testują metodę [GetCotisations]
  • wiersze 45–53: testują metodę [GetEmploye]
  • wiersze 56–65: testują metodę [GetEmploye] w przypadku wystąpienia wyjątku.

Podczas generowania projektu tworzone są pliki DLL i [pam-dao-spring-nhibernate.dll] w folderze [bin/Release]:

Wczytujemy pliki DLL i [pam-dao-spring-nhibernate.dll] za pomocą narzędzia [NUnit-Gui] w wersji 2.5 i przeprowadzamy testy:

Image

Powyższe testy zakończyły się powodzeniem.

2.2.4. Generacj z DLL z warstwy [dao]

Po napisaniu i przetestowaniu klasy [PamDaoNHibernate] wygenerujemy klasę DLL z warstwy [dao] w następujący sposób:

  • [1] – programy testowe są wykluczone z kompilacji projektu
  • [2,3], konfiguracja projektu
  • [4], generowanie projektu
  • plik DLL jest generowany w folderze [bin/Release] [5].