Sdílet prostřednictvím


Iterace č. 4 – vytvoření volně spárované aplikace (VB)

od Microsoftu

Stáhnout kód

V této čtvrté iteraci využijeme několik vzorů návrhu softwaru, abychom usnadnili údržbu a úpravu aplikace Contact Manager. Například refaktorujeme aplikaci tak, aby používala vzor Úložiště a injektáž závislostí.

Vytvoření ASP.NET aplikace MVC (VB) pro správu kontaktů

V této sérii kurzů sestavíme celou aplikaci Pro správu kontaktů od začátku do konce. Aplikace Contact Manager umožňuje ukládat kontaktní informace – jména, telefonní čísla a e-mailové adresy – pro seznam lidí.

Aplikaci sestavíme pomocí několika iterací. S každou iterací aplikaci postupně vylepšujeme. Cílem tohoto přístupu k vícenásobné iteraci je pochopit důvod každé změny.

  • Iterace č. 1 – vytvořte aplikaci. V první iteraci vytvoříme Správce kontaktů nejjednodušším možným způsobem. Přidáváme podporu pro základní databázové operace: vytvoření, čtení, aktualizace a odstranění (CRUD).

  • Iterace č. 2 – aby aplikace vypadala hezky. V této iteraci vylepšujeme vzhled aplikace úpravou výchozí stránky předlohy zobrazení ASP.NET zobrazení MVC a šablony stylů CSS.

  • Iterace č. 3 – přidejte ověření formuláře. Ve třetí iteraci přidáme základní ověření formuláře. Bráníme uživatelům v odesílání formuláře bez vyplnění požadovaných polí formuláře. Ověřujeme také e-mailové adresy a telefonní čísla.

  • Iterace č. 4 – Nastavte aplikaci volně provázanou. V této čtvrté iteraci využijeme několik vzorů návrhu softwaru, abychom usnadnili údržbu a úpravu aplikace Contact Manager. Například refaktorujeme aplikaci tak, aby používala vzor Úložiště a injektáž závislostí.

  • Iterace č. 5 – vytvoření testů jednotek V páté iteraci usnadňujeme údržbu a úpravy naší aplikace přidáním testů jednotek. Napodobení tříd datového modelu a sestavení testů jednotek pro naše kontrolery a logiku ověřování.

  • Iterace č. 6 – použijte vývoj řízený testy. V této šesté iteraci přidáme do naší aplikace nové funkce tím, že nejprve napíšeme testy jednotek a napíšeme kód proti testům jednotek. V této iteraci přidáme skupiny kontaktů.

  • Iterace č. 7 – přidání funkcí Ajax. V sedmé iteraci vylepšujeme rychlost odezvy a výkon naší aplikace přidáním podpory pro Ajax.

Tato iterace

V této čtvrté iteraci aplikace Contact Manager refaktorujeme aplikaci, aby byla aplikace volněji svázána. Pokud je aplikace volně svázána, můžete upravit kód v jedné části aplikace, aniž byste museli upravovat kód v jiných částech aplikace. Volně propojené aplikace jsou odolnější vůči změnám.

V současné době je veškerá logika přístupu k datům a ověřování používaná aplikací Contact Manager obsažena v třídách kontroleru. To je špatný nápad. Kdykoli potřebujete upravit jednu část aplikace, riskujete, že chyby zavedou do jiné části aplikace. Pokud například upravíte logiku ověřování, riskujete, že do logiky přístupu k datům nebo kontroleru zavedou nové chyby.

Poznámka

(SRP), třída by nikdy neměla mít více než jeden důvod ke změně. Míchání kontroleru, ověřování a logiky databáze je masivním porušením principu jednotné odpovědnosti.

Existuje několik důvodů, proč můžete aplikaci upravit. Možná budete muset do aplikace přidat novou funkci, možná budete muset opravit chybu v aplikaci nebo budete muset upravit způsob implementace funkce aplikace. Aplikace jsou zřídka statické. Mají tendenci růst a mutovat v průběhu času.

Představte si například, že se rozhodnete změnit způsob implementace vrstvy přístupu k datům. V současné chvíli aplikace Contact Manager používá pro přístup k databázi rozhraní Microsoft Entity Framework. Můžete se ale rozhodnout migrovat na novou nebo alternativní technologii přístupu k datům, jako je ADO.NET Data Services nebo NHibernate. Vzhledem k tomu, že kód pro přístup k datům není izolovaný od kódu ověřování a kontroleru, neexistuje způsob, jak upravit přístupový kód k datům ve vaší aplikaci bez úpravy jiného kódu, který přímo nesouvisí s přístupem k datům.

Pokud je aplikace volně svázána, můžete na druhé straně provádět změny v jedné části aplikace, aniž byste se dotýkali jiných částí aplikace. Můžete například přepínat technologie přístupu k datům beze změny logiky ověřování nebo kontroleru.

V této iteraci využijeme několik vzorů návrhu softwaru, které nám umožňují refaktorovat aplikaci Contact Manager do volněji propojené aplikace. Až skončíme, správce kontaktů nebude dělat nic, co předtím neudělal. V budoucnu ale budeme moct aplikaci snadněji změnit.

Poznámka

Refaktoring je proces přepsání aplikace takovým způsobem, aby neztratil žádné existující funkce.

Použití vzoru návrhu softwaru úložiště

Naší první změnou je využít vzor návrhu softwaru označovaný jako model úložiště. Model Úložiště použijeme k izolaci přístupového kódu k datům od zbytku naší aplikace.

Implementace modelu úložiště vyžaduje, abychom dokončili následující dva kroky:

  1. Vytvoření rozhraní
  2. Vytvoření konkrétní třídy, která implementuje rozhraní

Nejprve musíme vytvořit rozhraní, které popisuje všechny metody přístupu k datům, které musíme provést. Rozhraní IContactManagerRepository je obsaženo v výpisu 1. Toto rozhraní popisuje pět metod: CreateContact(), DeleteContact(), EditContact(), GetContact a ListContacts().

Výpis 1 – Models\IContactManagerRepository.vb

Public Interface IContactManagerRepository
Function CreateContact(ByVal contactToCreate As Contact) As Contact
Sub DeleteContact(ByVal contactToDelete As Contact)
Function EditContact(ByVal contactToUpdate As Contact) As Contact
Function GetContact(ByVal id As Integer) As Contact
Function ListContacts() As IEnumerable(Of Contact)
End Interface

Dále musíme vytvořit konkrétní třídu, která implementuje rozhraní IContactManagerRepository. Vzhledem k tomu, že pro přístup k databázi používáme Microsoft Entity Framework, vytvoříme novou třídu s názvem EntityContactManagerRepository. Tato třída je obsažena ve výpisu 2.

Výpis 2 – Models\EntityContactManagerRepository.vb

Public Class EntityContactManagerRepository
Implements IContactManagerRepository

Private _entities As New ContactManagerDBEntities()

Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerRepository.GetContact
    Return (From c In _entities.ContactSet _
            Where c.Id = id _
            Select c).FirstOrDefault()
End Function


Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerRepository.ListContacts
    Return _entities.ContactSet.ToList()
End Function


Public Function CreateContact(ByVal contactToCreate As Contact) As Contact Implements IContactManagerRepository.CreateContact
    _entities.AddToContactSet(contactToCreate)
    _entities.SaveChanges()
    Return contactToCreate
End Function


Public Function EditContact(ByVal contactToEdit As Contact) As Contact Implements IContactManagerRepository.EditContact
    Dim originalContact = GetContact(contactToEdit.Id)
    _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit)
    _entities.SaveChanges()
    Return contactToEdit
End Function


Public Sub DeleteContact(ByVal contactToDelete As Contact) Implements IContactManagerRepository.DeleteContact
    Dim originalContact = GetContact(contactToDelete.Id)
    _entities.DeleteObject(originalContact)
    _entities.SaveChanges()
End Sub

End Class

Všimněte si, že Třída EntityContactManagerRepository implementuje rozhraní IContactManagerRepository. Třída implementuje všech pět metod popsaných tímto rozhraním.

Možná se divíte, proč se musíme obtěžovat s rozhraním. Proč potřebujeme vytvořit rozhraní i třídu, která ho implementuje?

Až na jednu výjimku bude zbytek naší aplikace pracovat s rozhraním, a ne s konkrétní třídou. Místo volání metod vystavených EntityContactManagerRepository třídy, budeme volat metody vystavené rozhraním IContactManagerRepository.

Tímto způsobem můžeme implementovat rozhraní s novou třídou, aniž bychom museli upravovat zbytek naší aplikace. Například k určitému budoucímu datu můžeme chtít implementovat DataServicesContactManagerRepository třídy, která implementuje rozhraní IContactManagerRepository. DataServicesContactManagerRepository Třída může používat ADO.NET Data Services pro přístup k databázi místo Microsoft Entity Framework.

Pokud je kód aplikace naprogramován proti rozhraní IContactManagerRepository místo konkrétní entityContactManagerRepository třídy, můžeme přepnout konkrétní třídy beze změny některého ze zbytku našeho kódu. Můžeme například přepnout z EntityContactManagerRepository třídy DataServicesContactManagerRepository třídy beze změny našeho přístupu k datům nebo logiky ověřování.

Programování proti rozhraním (abstrakcí) místo konkrétních tříd činí naši aplikaci odolnější vůči změnám.

Poznámka

V sadě Visual Studio můžete rychle vytvořit rozhraní z konkrétní třídy výběrem možnosti nabídky Refaktorovat, Extrahovat rozhraní. Můžete například nejprve vytvořit třídu EntityContactManagerRepository a pak použít Extrahovat rozhraní k automatickému vygenerování rozhraní IContactManagerRepository.

Použití vzoru návrhu softwaru pro injektáž závislostí

Teď, když jsme migrovali přístupový kód k datům do samostatné třídy Repository, musíme upravit kontroler kontaktů tak, aby tuto třídu používal. K použití třídy Repository v našem kontroleru využijeme vzor návrhu softwaru s názvem Dependency Injection.

Upravený kontroler kontaktu je obsažen ve výpisu 3.

Výpis 3 – Controllers\ContactController.vb

Public Class ContactController
    Inherits System.Web.Mvc.Controller

    Private _repository As IContactManagerRepository 

    Sub New()
        Me.New(new EntityContactManagerRepository())
    End Sub

    Sub New(repository As IContactManagerRepository)
        _repository = repository
    End Sub

    Protected Sub ValidateContact(contactToValidate As Contact)
        If contactToValidate.FirstName.Trim().Length = 0 Then
            ModelState.AddModelError("FirstName", "First name is required.")
        End If
        If contactToValidate.LastName.Trim().Length = 0 Then
            ModelState.AddModelError("LastName", "Last name is required.")
        End If
        If (contactToValidate.Phone.Length > 0 AndAlso Not Regex.IsMatch(contactToValidate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
            ModelState.AddModelError("Phone", "Invalid phone number.")
        End If        
        If (contactToValidate.Email.Length > 0 AndAlso  Not Regex.IsMatch(contactToValidate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
            ModelState.AddModelError("Email", "Invalid email address.")
        End If
    End Sub

    Function Index() As ActionResult
        Return View(_repository.ListContacts())
    End Function

    Function Create() As ActionResult
        Return View()
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
        ' Validation logic
        ValidateContact(contactToCreate)
        If Not ModelState.IsValid Then
            Return View()
        End If

        ' Database logic
        Try
            _repository.CreateContact(contactToCreate)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

    Function Edit(ByVal id As Integer) As ActionResult
        Return View(_repository.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Edit(ByVal contactToEdit As Contact) As ActionResult
        ' Validation logic
        ValidateContact(contactToEdit)
        If Not ModelState.IsValid Then
            Return View()
        End If

        ' Database logic
        Try
            _repository.EditContact(contactToEdit)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

    Function Delete(ByVal id As Integer) As ActionResult
        Return View(_repository.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Delete(ByVal contactToDelete As Contact) As ActionResult
        Try
            _repository.DeleteContact(contactToDelete)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

End Class

Všimněte si, že kontroler kontaktu v seznamu 3 má dva konstruktory. První konstruktor předává konkrétní instanci IContactManagerRepository rozhraní do druhého konstruktoru. Třída kontroleru kontaktu používá injektáž závislostí konstruktoru.

Jediné místo, které EntityContactManagerRepository třídy je v prvním konstruktoru. Zbytek třídy používá IContactManagerRepository rozhraní místo konkrétní EntityContactManagerRepository třídy.

To usnadňuje přepínání implementací IContactManagerRepository třídy v budoucnu. Pokud chcete použít Třídu DataServicesContactRepository místo třídy EntityContactManagerRepository, stačí upravit první konstruktor.

Injektáž závislostí konstruktoru také velmi testuje třídu kontroleru kontaktů. V testech jednotek můžete vytvořit instanci kontroleru kontaktu předáním napodobené implementace IContactManagerRepository třídy. Tato funkce injektáže závislostí bude pro nás velmi důležitá v další iteraci při sestavování testů jednotek pro aplikaci Contact Manager.

Poznámka

Pokud chcete zcela oddělit třídu kontroleru kontaktů od konkrétní implementace rozhraní IContactManagerRepository pak můžete využít rozhraní, které podporuje injektáž závislostí, jako je Například StructureMap nebo Microsoft Entity Framework (MEF). Když využijete architekturu injektáže závislostí, nemusíte v kódu odkazovat na konkrétní třídu.

Vytvoření vrstvy služby

Možná jste si všimli, že naše logika ověřování je stále smíšená s logikou kontroleru ve třídě upraveného kontroleru ve výpisu 3. Ze stejného důvodu, jako je vhodné izolovat logiku přístupu k datům, je vhodné izolovat logiku ověřování.

Abychom tento problém vyřešili, můžeme vytvořit samostatnou vrstvu služby. Vrstva služby je samostatná vrstva, kterou můžeme vložit mezi třídu kontroleru a úložiště. Vrstva služby obsahuje naši obchodní logiku včetně veškeré logiky ověřování.

Služba ContactManagerService je obsažena ve výpisu 4. Obsahuje logiku ověřování z třídy Kontroleru kontaktů.

Výpis 4 – Models\ContactManagerService.vb

Public Class ContactManagerService
Implements IContactManagerService

Private _validationDictionary As IValidationDictionary
Private _repository As IContactManagerRepository


Public Sub New(ByVal validationDictionary As IValidationDictionary)
    Me.New(validationDictionary, New EntityContactManagerRepository())
End Sub


Public Sub New(ByVal validationDictionary As IValidationDictionary, ByVal repository As IContactManagerRepository)
    _validationDictionary = validationDictionary
    _repository = repository
End Sub


Public Function ValidateContact(ByVal contactToValidate As Contact) As Boolean
    If contactToValidate.FirstName.Trim().Length = 0 Then
        _validationDictionary.AddError("FirstName", "First name is required.")
    End If
    If contactToValidate.LastName.Trim().Length = 0 Then
        _validationDictionary.AddError("LastName", "Last name is required.")
    End If
    If contactToValidate.Phone.Length > 0 AndAlso (Not Regex.IsMatch(contactToValidate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}")) Then
        _validationDictionary.AddError("Phone", "Invalid phone number.")
    End If
    If contactToValidate.Email.Length > 0 AndAlso (Not Regex.IsMatch(contactToValidate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$")) Then
        _validationDictionary.AddError("Email", "Invalid email address.")
    End If
    Return _validationDictionary.IsValid
End Function


#Region "IContactManagerService Members"

Public Function CreateContact(ByVal contactToCreate As Contact) As Boolean Implements IContactManagerService.CreateContact
    ' Validation logic
    If Not ValidateContact(contactToCreate) Then
        Return False
    End If

    ' Database logic
    Try
        _repository.CreateContact(contactToCreate)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function EditContact(ByVal contactToEdit As Contact) As Boolean Implements IContactManagerService.EditContact
    ' Validation logic
    If Not ValidateContact(contactToEdit) Then
        Return False
    End If

    ' Database logic
    Try
        _repository.EditContact(contactToEdit)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function DeleteContact(ByVal contactToDelete As Contact) As Boolean Implements IContactManagerService.DeleteContact
    Try
        _repository.DeleteContact(contactToDelete)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerService.GetContact
    Return _repository.GetContact(id)
End Function

Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerService.ListContacts
    Return _repository.ListContacts()
End Function

#End Region
End Class

Všimněte si, že konstruktor pro ContactManagerService vyžaduje ValidationDictionary. Vrstva služby komunikuje s vrstvou kontroleru prostřednictvím tohoto ověřovacího slovníku. Ověřovací slovník probereme podrobně v následující části, kde probereme model Dekorator.

Všimněte si také, že ContactManagerService implementuje rozhraní IContactManagerService. Měli byste se vždy snažit programovat proti rozhraním místo konkrétních tříd. Jiné třídy v aplikaci Contact Manager nepracují přímo s třídou ContactManagerService. Místo toho, s jednou výjimkou, zbytek aplikace Contact Manager je naprogramován na IContactManagerService rozhraní.

Rozhraní IContactManagerService je obsaženo ve výpisu 5.

Výpis 5 – Models\IContactManagerService.vb

Public Interface IContactManagerService
Function CreateContact(ByVal contactToCreate As Contact) As Boolean
Function DeleteContact(ByVal contactToDelete As Contact) As Boolean
Function EditContact(ByVal contactToEdit As Contact) As Boolean
Function GetContact(ByVal id As Integer) As Contact
Function ListContacts() As IEnumerable(Of Contact)
End Interface

Upravená třída kontroleru kontaktů je obsažena ve výpisu 6. Všimněte si, že kontroler kontaktů už nepracuje s úložištěm ContactManager. Místo toho kontakt kontroler komunikuje se službou ContactManager. Každá vrstva je co nejvíce izolovaná od ostatních vrstev.

Výpis 6 – Controllers\ContactController.vb

Public Class ContactController
    Inherits System.Web.Mvc.Controller

    Private _service As IContactManagerService 

    Sub New()
        _service = new ContactManagerService(New ModelStateWrapper(ModelState))
    End Sub

    Sub New(service As IContactManagerService)
        _service = service
    End Sub

    Function Index() As ActionResult
        Return View(_service.ListContacts())
    End Function

    Function Create() As ActionResult
        Return View()
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
        If _service.CreateContact(contactToCreate) Then
            Return RedirectToAction("Index")        
        End If
        Return View()
    End Function

    Function Edit(ByVal id As Integer) As ActionResult
        Return View(_service.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Edit(ByVal contactToEdit As Contact) As ActionResult
        If _service.EditContact(contactToEdit) Then
            Return RedirectToAction("Index")        
        End If
        Return View()
    End Function

    Function Delete(ByVal id As Integer) As ActionResult
        Return View(_service.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Delete(ByVal contactToDelete As Contact) As ActionResult
        If _service.DeleteContact(contactToDelete) Then
            return RedirectToAction("Index")
        End If
        Return View()
    End Function

End Class

Naše aplikace už není v souladu s principem SRP (Single Responsibility Principle). Kontroleru kontaktů ve výpisu 6 byly odebrány všechny zodpovědnosti kromě řízení toku provádění aplikace. Veškerá logika ověřování byla odebrána z kontroleru kontaktu a vložena do vrstvy služby. Veškerá logika databáze byla vložena do vrstvy úložiště.

Použití vzoru dekorátoru

Chceme mít možnost zcela oddělit vrstvu služby od vrstvy kontroleru. V zásadě bychom měli být schopni zkompilovat naši vrstvu služby v samostatném sestavení, než je vrstva kontroleru, aniž bychom museli přidávat odkaz na naši aplikaci MVC.

Naše vrstva služby ale musí být schopná předávat chybové zprávy ověřování zpět vrstvě kontroleru. Jak můžeme vrstvě služby povolit, aby předávala chybové zprávy ověřování bez propojení kontroleru a vrstvy služby? Můžeme využít vzor návrhu softwaru s názvem Dekorátor.

Kontroler používá ModelStateDictionary s názvem ModelState k reprezentaci chyb ověření. Proto můžete být v pokušení předat ModelState z vrstvy kontroleru do vrstvy služby. Pokud ale použijete ModelState ve vrstvě služby, bude vaše vrstva služby závislá na funkci architektury ASP.NET MVC. To by bylo špatné, protože jednou budete chtít použít vrstvu služby s aplikací WPF místo aplikace ASP.NET MVC. V takovém případě byste nechtěli odkazovat na architekturu ASP.NET MVC pro použití třídy ModelStateDictionary.

Model Dekorátor umožňuje zabalit existující třídu do nové třídy, aby bylo možné implementovat rozhraní. Náš projekt Contact Manageru zahrnuje třídu ModelStateWrapper obsaženou ve výpisu 7. ModelStateWrapper Třída implementuje rozhraní ve výpisu 8.

Výpis 7 – Models\Validation\ModelStateWrapper.vb

Public Class ModelStateWrapper
Implements IValidationDictionary

Private _modelState As ModelStateDictionary

Public Sub New(ByVal modelState As ModelStateDictionary)
    _modelState = modelState
End Sub

Public Sub AddError(ByVal key As String, ByVal errorMessage As String) Implements IValidationDictionary.AddError
    _modelState.AddModelError(key, errorMessage)
End Sub

Public ReadOnly Property IsValid() As Boolean Implements IValidationDictionary.IsValid
    Get
        Return _modelState.IsValid
    End Get
End Property

End Class

Výpis 8 – Models\Validation\IValidationDictionary.vb

Public Interface IValidationDictionary

Sub AddError(ByVal key As String, ByVal errorMessage As String)
ReadOnly Property IsValid() As Boolean

End Interface

Pokud se na výpis 5 podíváte zblízka, uvidíte, že vrstva služby ContactManager používá výhradně rozhraní IValidationDictionary. Služba Není závislá na ModelStateDictionary třídy. Když kontroler kontaktů vytvoří službu ContactManager, zabalí kontroler modelState takto:

_service = new ContactManagerService(New ModelStateWrapper(ModelState))

Souhrn

V této iteraci jsme do aplikace Contact Manager nepřidali žádné nové funkce. Cílem této iterace bylo refaktorovat aplikaci Contact Manager tak, aby se snadněji udržovala a upravovala.

Nejprve jsme implementovali vzor návrhu softwaru repository. Veškerý kód pro přístup k datům jsme migrovali do samostatné třídy úložiště ContactManager.

Také jsme oddělili logiku ověřování od logiky kontroleru. Vytvořili jsme samostatnou vrstvu služby, která obsahuje veškerý ověřovací kód. Vrstva kontroleru komunikuje s vrstvou služby a vrstva služby komunikuje s vrstvou úložiště.

Při vytváření vrstvy služby jsme využili vzor Decorator k izolování modelu ModelState od vrstvy služby. V naší vrstvě služby jsme naprogramovali rozhraní IValidationDictionary místo modelState.

Nakonec jsme využili vzor návrhu softwaru s názvem Model injektáže závislostí. Tento model nám umožňuje programovat proti rozhraním (abstrakcím) místo konkrétních tříd. Implementace vzoru návrhu injektáže závislostí také zpřístupňuje testování našeho kódu. V další iteraci přidáme do projektu testy jednotek.