Sdílet prostřednictvím


Iterace č. 6 – použití vývoje řízeného testy (VB)

od Microsoftu

Stáhnout kód

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ů.

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 předchozí iteraci aplikace Contact Manager jsme vytvořili testy jednotek, které poskytují bezpečnostní síť pro náš kód. Motivací k vytvoření testů jednotek bylo, aby byl náš kód odolnější vůči změnám. Po provedení testů jednotek můžeme s radostí provést jakoukoli změnu kódu a okamžitě zjistit, jestli jsme neporušili stávající funkce.

V této iteraci používáme testy jednotek pro zcela jiný účel. V této iteraci používáme testy jednotek jako součást filozofie návrhu aplikace označované jako vývoj řízený testy. Když si procvičíte vývoj řízený testy, nejprve napíšete testy a pak napíšete kód proti testům.

Přesněji řečeno, při nácviku vývoje řízeného testy existují tři kroky, které dokončíte při vytváření kódu (červený/ zelený/refaktoring):

  1. Zápis testu jednotek, který selže (červený)
  2. Napište kód, který projde testem jednotek (zelená)
  3. Refaktoring kódu (Refaktoring)

Nejprve napíšete test jednotek. Test jednotek by měl vyjádřit váš záměr, jak očekáváte, že se váš kód bude chovat. Při prvním vytvoření testu jednotek by měl test jednotek selhat. Test by měl selhat, protože jste ještě nenapsali žádný kód aplikace, který splňuje test.

Dále napíšete dostatek kódu, aby test jednotek prošel. Cílem je napsat kód nejlínějším, nejsloppijším a nejrychlejším možným způsobem. Neměli byste ztrácet čas přemýšlením o architektuře vaší aplikace. Místo toho byste se měli zaměřit na napsání minimálního množství kódu potřebného ke splnění záměru vyjádřeného testem jednotek.

Nakonec, jakmile napíšete dostatek kódu, můžete se vrátit zpět a zvážit celkovou architekturu vaší aplikace. V tomto kroku přepíšete (refaktorujete) kód tak, že využijete vzory návrhu softwaru – například vzor úložiště – tak, aby byl váš kód lépe udržovatelný. V tomto kroku můžete nebojácně přepsat kód, protože váš kód je pokrytý testy jednotek.

Existuje mnoho výhod, které vyplývají z praktického testování řízeného vývoje. Za prvé, testem řízený vývoj vás nutí zaměřit se na kód, který je ve skutečnosti potřeba napsat. Vzhledem k tomu, že se neustále soustředíte jen na psaní dostatečného množství kódu pro absolvování konkrétního testu, zabráníte tomu, abyste bloudili do plevelů a napsali obrovské množství kódu, který nikdy nebudete používat.

Za druhé, metodika návrhu "test first" vás přinutí psát kód z pohledu toho, jak se bude váš kód používat. Jinými slovy, při nácviku vývoje řízeného testy neustále píšete testy z pohledu uživatele. Vývoj řízený testy proto může vést k čistším a srozumitelnějším rozhraním API.

A konečně, testem řízený vývoj vás přinutí psát testy jednotek jako součást normálního procesu psaní aplikace. S tím, jak se blíží konečný termín projektu, je testování obvykle první věcí, která jde ven. Při procvičování vývoje řízeného testy na druhou stranu je pravděpodobnější, že budete při psaní testů jednotek výkonnější, protože vývoj řízený testováním dělá testy jednotek ústředním pro proces vytváření aplikace.

Poznámka

Pokud se chcete dozvědět více o vývoji řízeném testy, doporučujeme přečíst si knihu Michaela Featherse Efektivní práce se starším kódem.

V této iteraci přidáme novou funkci do aplikace Contact Manager. Přidáváme podporu pro skupiny kontaktů. Skupiny kontaktů můžete použít k uspořádání kontaktů do kategorií, jako jsou třeba skupiny Pro firmy a Přátelé.

Tuto novou funkci přidáme do naší aplikace podle procesu vývoje řízeného testováním. Nejprve napíšeme testy jednotek a do těchto testů napíšeme veškerý kód.

Co se testuje

Jak jsme probrali v předchozí iteraci, obvykle nepíšete testy jednotek pro logiku přístupu k datům ani logiku zobrazení. Nepíšete testy jednotek pro logiku přístupu k datům, protože přístup k databázi je poměrně pomalá operace. Nepíšete testy jednotek pro logiku zobrazení, protože přístup k zobrazení vyžaduje spuštění webového serveru, což je poměrně pomalá operace. Neměli byste psát test jednotek, pokud se test nedá spustit znovu a znovu velmi rychle.

Vzhledem k tomu, že vývoj řízený testy se řídí testy jednotek, zaměřujeme se zpočátku na psaní kontroleru a obchodní logiky. Vyhýbáme se dotykům databáze nebo zobrazení. Až do konce tohoto kurzu nebudeme upravovat databázi ani vytvářet naše zobrazení. Začneme tím, co se dá otestovat.

Vytváření uživatelských scénářů

Při nácviku vývoje řízeného testy vždy začnete napsáním testu. To okamžitě vyvolává otázku: Jak se rozhodnete, jaký test napíšete jako první? Pokud chcete na tuto otázku odpovědět, měli byste napsat sadu uživatelských scénářů.

Uživatelský příběh je velmi stručný (obvykle jedna věta) popis požadavku na software. Měl by být netechnického popisu požadavku napsaného z pohledu uživatele.

Tady je sada uživatelských scénářů, které popisují funkce vyžadované novou funkcí skupiny kontaktů:

  1. Uživatel může zobrazit seznam skupin kontaktů.
  2. Uživatel může vytvořit novou skupinu kontaktů.
  3. Uživatel může odstranit existující skupinu kontaktů.
  4. Uživatel může vybrat skupinu kontaktů při vytváření nového kontaktu.
  5. Uživatel může vybrat skupinu kontaktů při úpravách existujícího kontaktu.
  6. V zobrazení Index se zobrazí seznam skupin kontaktů.
  7. Když uživatel klikne na skupinu kontaktů, zobrazí se seznam odpovídajících kontaktů.

Všimněte si, že tento seznam uživatelských scénářů je pro zákazníka zcela srozumitelný. O technických podrobnostech implementace není žádná zmínka.

V průběhu sestavování aplikace může být sada uživatelských scénářů ještě přesnější. Uživatelský scénář můžete rozdělit do několika scénářů (požadavky). Můžete se například rozhodnout, že vytvoření nové skupiny kontaktů by mělo zahrnovat ověření. Odeslání skupiny kontaktů bez jména by mělo vrátit chybu ověření.

Po vytvoření seznamu uživatelských scénářů jste připraveni napsat první test jednotek. Začneme vytvořením testu jednotek pro zobrazení seznamu skupin kontaktů.

Výpis skupin kontaktů

Naším prvním uživatelským příběhem je, že uživatel by měl být schopen zobrazit seznam skupin kontaktů. Musíme tento příběh vyjádřit testem.

Vytvořte nový test jednotek tak, že kliknete pravým tlačítkem na složku Controllers v projektu ContactManager.Tests, vyberete Přidat, Nový test a vyberete šablonu Test jednotek (viz Obrázek 1). Pojmenujte nový test jednotek GroupControllerTest.vb a klikněte na tlačítko OK .

Přidání testu jednotek GroupControllerTest

Obrázek 01: Přidání testu jednotek GroupControllerTest (kliknutím zobrazíte obrázek v plné velikosti)

Náš první test jednotky je obsažen ve výpisu 1. Tento test ověří, že metoda Index() kontroleru skupiny vrátí sadu skupin. Test ověří, že se v zobrazení dat vrátí kolekce skupin.

Výpis 1 – Controllers\GroupControllerTest.vb

Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports System.Web.Mvc

<TestClass()> _
Public Class GroupControllerTest

    <TestMethod()> _
    Public Sub Index()
        ' Arrange
        Dim controller = New GroupController()

        ' Act
        Dim result = CType(controller.Index(), ViewResult)

        ' Assert
        Assert.IsInstanceOfType(result.ViewData.Model, GetType(IEnumerable(Of Group)))
    End Sub
End Class

Při prvním zadání kódu v seznamu 1 v sadě Visual Studio se zobrazí spousta červených vlnovek. Nevytvořili jsme třídy GroupController nebo Group.

V tomto okamžiku nemůžeme ani sestavit aplikaci, takže nemůžeme provést první test jednotek. To je dobrý. To se počítá jako neúspěšný test. Proto teď máme oprávnění začít psát kód aplikace. K provedení testu potřebujeme napsat dostatek kódu.

Třída kontroleru skupiny v výpisu 2 obsahuje úplné minimum kódu potřebného ke absolvování testu jednotek. Akce Index() vrátí staticky kódovaný seznam Skupin (třída Group je definována ve výpisu 3).

Výpis 2 – Controllers\GroupController.vb

Public Class GroupController
    Inherits System.Web.Mvc.Controller

    Function Index() As ActionResult
        Dim groups = new List(Of Group)
        Return View(groups)
    End Function

End Class

Výpis 3 – Models\Group.vb

Public Class Group

End Class

Po přidání tříd GroupController a Group do našeho projektu se náš první test jednotek úspěšně dokončí (viz obrázek 2). Provedli jsme minimální práci potřebnou pro úspěšné absolvování testu. Je čas oslavit.

Úspěch!

Obrázek 02: Úspěch! (Kliknutím zobrazíte obrázek v plné velikosti.)

Vytváření skupin kontaktů

Teď můžeme přejít k druhému uživatelskému scénáři. Musíme být schopni vytvořit nové skupiny kontaktů. Tento záměr musíme vyjádřit testem.

Test ve výpisu 4 ověří, že volání metody Create() s novou skupinou přidá skupinu do seznamu skupin vrácených metodou Index(). Jinými slovy, pokud vytvořím novou skupinu, měla bych mít možnost získat novou skupinu zpět ze seznamu skupin vrácených metodou Index().

Výpis 4 – Controllers\GroupControllerTest.vb

<TestMethod> _
Public Sub Create()
    ' Arrange
    Dim controller = New GroupController()

    ' Act
    Dim groupToCreate = New Group()
    controller.Create(groupToCreate)

    ' Assert
    Dim result = CType(controller.Index(), ViewResult)
    Dim groups = CType(result.ViewData.Model, IEnumerable(Of Group))
    CollectionAssert.Contains(groups.ToList(), groupToCreate)
End Sub

Test ve výpisu 4 volá metodu Group controller Create() s novou skupinou kontaktů. Dále test ověří, že volání metody Index() kontroleru skupiny vrátí novou skupinu v zobrazení dat.

Upravený kontroler skupiny v seznamu 5 obsahuje úplné minimum změn potřebných pro úspěšné úspěšné provedení nového testu.

Výpis 5 – Controllers\GroupController.vb

Public Class GroupController
Inherits Controller

Private _groups As IList(Of Group) = New List(Of Group)()

Public Function Index() As ActionResult
    Return View(_groups)
End Function

Public Function Create(ByVal groupToCreate As Group) As ActionResult
    _groups.Add(groupToCreate)
    Return RedirectToAction("Index")

End Function
End Class

Kontroler skupiny v seznamu 5 má novou akci Create(). Tato akce přidá skupinu do kolekce Skupin. Všimněte si, že akce Index() byla upravena tak, aby vracela obsah kolekce Skupin.

Opět jsme provedli minimální množství práce potřebné ke absolvování testu jednotek. Po provedení těchto změn v kontroleru skupiny všechny testy jednotek projdou.

Přidání ověření

Tento požadavek nebyl explicitně uveden v uživatelském scénáři. Je však rozumné požadovat, aby skupina měla název. Jinak by uspořádání kontaktů do skupin nebylo moc užitečné.

Výpis 6 obsahuje nový test, který vyjadřuje tento záměr. Tento test ověří, že při pokusu o vytvoření skupiny bez zadání názvu se zobrazí chybová zpráva ověření ve stavu modelu.

Výpis 6 – Controllers\GroupControllerTest.vb

<TestMethod> _
Public Sub CreateRequiredName()
    ' Arrange
    Dim controller = New GroupController()

    ' Act
    Dim groupToCreate As New Group()
    groupToCreate.Name = String.Empty
    Dim result = CType(controller.Create(groupToCreate), ViewResult)

    ' Assert
    Dim [error] = result.ViewData.ModelState("Name").Errors(0)
    Assert.AreEqual("Name is required.", [error].ErrorMessage)
End Sub

Abychom tomuto testu vyhověli, musíme do naší třídy Group přidat vlastnost Name (viz Výpis 7). Kromě toho musíme přidat malou část ověřovací logiky do akce Vytvořit() kontroleru skupiny (viz Výpis 8).

Výpis 7 – Modely\Group.vb

Public Class Group

    Private _name As String

    Public Property Name() As String
    Get
        Return _name
    End Get
    Set(ByVal value As String)
        _name = value
    End Set
End Property

End Class

Výpis 8 – Controllers\GroupController.vb

Public Function Create(ByVal groupToCreate As Group) As ActionResult
    ' Validation logic
    If groupToCreate.Name.Trim().Length = 0 Then
    ModelState.AddModelError("Name", "Name is required.")
    Return View("Create")
    End If

    ' Database logic
    _groups.Add(groupToCreate)
    Return RedirectToAction("Index")
End Function

Všimněte si, že akce Vytvořit() kontroleru skupiny teď obsahuje logiku ověřování i databáze. V současné době se databáze používaná kontrolerem skupiny skládá z pouhé kolekce v paměti.

Refaktoring času

Třetím krokem v části Red/Green/Refactor je část Refaktoring. V tomto okamžiku musíme ustoupit od našeho kódu a zvážit, jak můžeme refaktorovat aplikaci, abychom vylepšili její návrh. Fáze refaktoringu je fáze, ve které usilovně přemýšlíme o nejlepším způsobu implementace principů a vzorů návrhu softwaru.

Náš kód můžeme libovolně upravovat, abychom vylepšili jeho návrh. Máme bezpečnostní síť testů jednotek, která nám brání v narušení stávajících funkcí.

Právě teď je náš kontroler skupiny nepořádek z hlediska dobrého návrhu softwaru. Kontroler skupiny obsahuje zamotané ověřování a kód pro přístup k datům. Abychom se vyhnuli porušení zásady jednotné odpovědnosti, musíme tyto záležitosti rozdělit do různých tříd.

Naše třída kontroleru refaktorované skupiny je obsažena ve výpisu 9. Kontroler byl upraven tak, aby používal vrstvu služby ContactManager. Jedná se o stejnou vrstvu služby, kterou používáme s kontrolerem kontaktů.

Výpis 10 obsahuje nové metody přidané do vrstvy služby ContactManager pro podporu ověřování, výpisu a vytváření skupin. Rozhraní IContactManagerService bylo aktualizováno tak, aby zahrnovalo nové metody.

Výpis 11 obsahuje novou FakeContactManagerRepository třídy, která implementuje IContactManagerRepository rozhraní. Na rozdíl od Třídy EntityContactManagerRepository, která také implementuje rozhraní IContactManagerRepository, naše nová třída FakeContactManagerRepository nekomunikuje s databází. FakeContactManagerRepository Třída používá kolekci v paměti jako proxy pro databázi. Tuto třídu použijeme v našich testech jednotek jako vrstvu falešného úložiště.

Výpis 9 – Controllers\GroupController.vb

Public Class GroupController
Inherits Controller

Private _service As IContactManagerService

Public Sub New()
    _service = New ContactManagerService(New ModelStateWrapper(Me.ModelState))

End Sub

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

Public Function Index() As ActionResult
    Return View(_service.ListGroups())
End Function


Public Function Create(ByVal groupToCreate As Group) As ActionResult
    If _service.CreateGroup(groupToCreate) Then
        Return RedirectToAction("Index")
    End If
    Return View("Create")
End Function

End Class

Výpis 10 – Controllers\ContactManagerService.vb

Public Function ValidateGroup(ByVal groupToValidate As Group) As Boolean
If groupToValidate.Name.Trim().Length = 0 Then
    _validationDictionary.AddError("Name", "Name is required.")
End If
Return _validationDictionary.IsValid
End Function

Public Function CreateGroup(ByVal groupToCreate As Group) As Boolean Implements IContactManagerService.CreateGroup
    ' Validation logic
    If Not ValidateGroup(groupToCreate) Then
        Return False
    End If

    ' Database logic
    Try
        _repository.CreateGroup(groupToCreate)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerService.ListGroups
    Return _repository.ListGroups()
End Function

Výpis 11 – Controllers\FakeContactManagerRepository.vb

Public Class FakeContactManagerRepository
Implements IContactManagerRepository

Private _groups As IList(Of Group) = New List(Of Group)()

#Region "IContactManagerRepository Members"

' Group methods

Public Function CreateGroup(ByVal groupToCreate As Group) As Group Implements IContactManagerRepository.CreateGroup
    _groups.Add(groupToCreate)
    Return groupToCreate
End Function

Public Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerRepository.ListGroups
    Return _groups
End Function

' Contact methods

Public Function CreateContact(ByVal contactToCreate As Contact) As Contact Implements IContactManagerRepository.CreateContact
    Throw New NotImplementedException()
End Function

Public Sub DeleteContact(ByVal contactToDelete As Contact) Implements IContactManagerRepository.DeleteContact
    Throw New NotImplementedException()
End Sub

Public Function EditContact(ByVal contactToEdit As Contact) As Contact Implements IContactManagerRepository.EditContact
    Throw New NotImplementedException()
End Function

Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerRepository.GetContact
    Throw New NotImplementedException()
End Function

Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerRepository.ListContacts
    Throw New NotImplementedException()
End Function

#End Region
End Class

Úprava rozhraní IContactManagerRepository vyžaduje použití k implementaci Metod CreateGroup() a ListGroups() v EntityContactManagerRepository třídy. Nejlínější a nejrychlejší způsob, jak to udělat, je přidat metody stub, které vypadají takto:

Public Function CreateGroup(groupToCreate As Group) As Group Implements IContactManagerRepository.CreateGroup

    throw New NotImplementedException()

End Function 

Public Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerRepository.ListGroups

    throw New NotImplementedException()

End Function

Nakonec tyto změny návrhu naší aplikace vyžadují, abychom v testech jednotek udělali nějaké změny. Při provádění testů jednotek teď musíme použít FakeContactManagerRepository. Aktualizovaná třída GroupControllerTest je obsažena ve výpisu 12.

Výpis 12 – Controllers\GroupControllerTest.vb

Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports System.Web.Mvc

<TestClass()> _
Public Class GroupControllerTest

    Private _repository As IContactManagerRepository
    Private _modelState As ModelStateDictionary
    Private _service As IContactManagerService

    <TestInitialize()> _
    Public Sub Initialize()
        _repository = New FakeContactManagerRepository()
        _modelState = New ModelStateDictionary()
        _service = New ContactManagerService(New ModelStateWrapper(_modelState), _repository)
    End Sub

    <TestMethod()> _
    Public Sub Index()
        ' Arrange
        Dim controller = New GroupController(_service)

        ' Act
        Dim result = CType(controller.Index(), ViewResult)

        ' Assert
        Assert.IsInstanceOfType(result.ViewData.Model, GetType(IEnumerable(Of Group)))
    End Sub

    <TestMethod()> _
    Public Sub Create()
        ' Arrange
        Dim controller = New GroupController(_service)

        ' Act
        Dim groupToCreate = New Group()
        groupToCreate.Name = "Business"
        controller.Create(groupToCreate)

        ' Assert
        Dim result = CType(controller.Index(), ViewResult)
        Dim groups = CType(result.ViewData.Model, IEnumerable(Of Group))
        CollectionAssert.Contains(groups.ToList(), groupToCreate)
    End Sub

    <TestMethod()> _
    Public Sub CreateRequiredName()
        ' Arrange
        Dim controller = New GroupController(_service)

        ' Act
        Dim groupToCreate = New Group()
        groupToCreate.Name = String.Empty
        Dim result = CType(controller.Create(groupToCreate), ViewResult)

        ' Assert
        Dim nameError = _modelState("Name").Errors(0)
        Assert.AreEqual("Name is required.", nameError.ErrorMessage)
    End Sub

End Class

Jakmile provedeme všechny tyto změny, všechny testy jednotek opět projdou. Dokončili jsme celý cyklus red/green/refactor. Implementovali jsme první dva uživatelské scénáře. Teď máme podpůrné testy jednotek pro požadavky vyjádřené v uživatelských příbězích. Implementace zbytku uživatelských scénářů zahrnuje opakování stejného cyklu red/green/refactor.

Úprava naší databáze

Bohužel, i když jsme splnili všechny požadavky vyjádřené našimi testy jednotek, naše práce není dokončena. Stále potřebujeme upravit naši databázi.

Potřebujeme vytvořit novou tabulku databáze skupiny. Postupujte takto:

  1. V okně Průzkumník serveru klikněte pravým tlačítkem na složku Tabulky a vyberte možnost nabídky Přidat novou tabulku.
  2. Zadejte dva sloupce popsané níže v tabulce Designer.
  3. Označte sloupec Id jako primární klíč a sloupec Identita.
  4. Kliknutím na ikonu diskety uložte novou tabulku s názvem Skupiny.

Název sloupce Datový typ Povolit hodnoty Null
Id int Ne
Název nvarchar(50) Ne

Dále musíme odstranit všechna data z tabulky Kontakty (jinak nebudeme moct vytvořit relaci mezi tabulkami Kontakty a Skupiny). Postupujte takto:

  1. Klikněte pravým tlačítkem na tabulku Kontakty a vyberte možnost nabídky Zobrazit data tabulky.
  2. Odstraňte všechny řádky.

Dále musíme definovat relaci mezi tabulkou databáze Groups a existující tabulkou databáze Kontakty. Postupujte takto:

  1. Poklikáním na tabulku Kontakty v okně Průzkumník serveru otevřete Designer tabulky.
  2. Do tabulky Kontakty přidejte nový celočíselný sloupec s názvem GroupId.
  3. Kliknutím na tlačítko Relace otevřete dialogové okno Relace cizích klíčů (viz Obrázek 3).
  4. Klikněte na tlačítko Přidat.
  5. Klikněte na tlačítko se třemi tečky, které se zobrazí vedle tlačítka Specifikace tabulky a sloupců.
  6. V dialogovém okně Tabulky a sloupce vyberte Skupiny jako tabulku primárního klíče a jako sloupec primárního klíče vyberte Id. Jako tabulku cizích klíčů vyberte Kontakty a jako sloupec cizího klíče vyberte GroupId (viz Obrázek 4). Klikněte na tlačítko OK.
  7. V části INSERT a UPDATE Specification (Specifikace vložení a aktualizace) vyberte hodnotu Cascade (Kaskáda) pro Delete Rule (Odstranit pravidlo).
  8. Kliknutím na tlačítko Zavřít zavřete dialogové okno Relace cizích klíčů.
  9. Kliknutím na tlačítko Uložit uložte změny do tabulky Kontakty.

Vytvoření relace databázové tabulky

Obrázek 03: Vytvoření relace databázové tabulky (kliknutím zobrazíte obrázek v plné velikosti)

Určení relací mezi tabulkami

Obrázek 04: Určení relací mezi tabulkami (kliknutím zobrazíte obrázek v plné velikosti)

Aktualizace datového modelu

Dále musíme aktualizovat datový model tak, aby představoval novou tabulku databáze. Postupujte takto:

  1. Poklikáním na soubor ContactManagerModel.edmx ve složce Models otevřete Designer Entity.
  2. Klikněte pravým tlačítkem na plochu Designer a vyberte možnost nabídky Aktualizovat model z databáze.
  3. V Průvodci aktualizací vyberte tabulku Skupiny a klikněte na tlačítko Dokončit (viz Obrázek 5).
  4. Klikněte pravým tlačítkem na entitu Skupiny a vyberte možnost nabídky Přejmenovat. Změňte název entity Groups na Skupina (singulární).
  5. Klikněte pravým tlačítkem na navigační vlastnost Skupiny, která se zobrazí v dolní části entity Kontakt. Změňte název navigační vlastnosti Skupiny na Group (singulární).

Aktualizace modelu Entity Framework z databáze

Obrázek 05: Aktualizace modelu Entity Framework z databáze (kliknutím zobrazíte obrázek v plné velikosti)

Po dokončení těchto kroků bude datový model představovat tabulky Kontakty i Skupiny. Designer entity by měly obsahovat obě entity (viz obrázek 6).

Designer entit zobrazující skupinu a kontakt

Obrázek 06: Entita Designer zobrazující skupinu a kontakt (kliknutím zobrazíte obrázek v plné velikosti)

Vytváření tříd úložiště

Dále musíme implementovat třídu úložiště. V průběhu této iterace jsme přidali několik nových metod do rozhraní IContactManagerRepository při psaní kódu pro splnění našich testů jednotek. Konečná verze rozhraní IContactManagerRepository je obsažena ve výpisu 14.

Výpis 14 – Models\IContactManagerRepository.vb

Public Interface IContactManagerRepository
' Contact methods
Function CreateContact(ByVal groupId As Integer, ByVal contactToCreate As Contact) As Contact
Sub DeleteContact(ByVal contactToDelete As Contact)
Function EditContact(ByVal groupId As Integer, ByVal contactToEdit As Contact) As Contact
Function GetContact(ByVal id As Integer) As Contact

' Group methods
Function CreateGroup(ByVal groupToCreate As Group) As Group
Function ListGroups() As IEnumerable(Of Group)
Function GetGroup(ByVal groupId As Integer) As Group
Function GetFirstGroup() As Group
Sub DeleteGroup(ByVal groupToDelete As Group)

End Interface

Ve skutečnosti jsme neimplementovali žádnou z metod souvisejících s prací se skupinami kontaktů v naší skutečné třídě EntityContactManagerRepository. V současné době EntityContactManagerRepository třídy má inzerované metody pro každou z metod skupiny kontaktů uvedených v IContactManagerRepository rozhraní. Například metoda ListGroups() aktuálně vypadá takto:

Public Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerRepository.ListGroups

    throw New NotImplementedException()

End Function

Metody se zástupným inzerováním nám umožnily zkompilovat aplikaci a projít testy jednotek. Nyní je však čas tyto metody skutečně implementovat. Konečná verze třídy EntityContactManagerRepository je obsažena v výpisu 13.

Výpis 13 – Models\EntityContactManagerRepository.vb

Public Class EntityContactManagerRepository
Implements IContactManagerRepository

Private _entities As New ContactManagerDBEntities()

' Contact methods

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

Public Function CreateContact(ByVal groupId As Integer, ByVal contactToCreate As Contact) As Contact Implements IContactManagerRepository.CreateContact
    ' Associate group with contact
    contactToCreate.Group = GetGroup(groupId)

    ' Save new contact
    _entities.AddToContactSet(contactToCreate)
    _entities.SaveChanges()
    Return contactToCreate
End Function

Public Function EditContact(ByVal groupId As Integer, ByVal contactToEdit As Contact) As Contact Implements IContactManagerRepository.EditContact
    ' Get original contact
    Dim originalContact = GetContact(contactToEdit.Id)

    ' Update with new group
    originalContact.Group = GetGroup(groupId)

    ' Save changes
    _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

    ' Group methods

Public Function CreateGroup(ByVal groupToCreate As Group) As Group Implements IContactManagerRepository.CreateGroup 
    _entities.AddToGroupSet(groupToCreate)
    _entities.SaveChanges()
    Return groupToCreate
End Function

Public Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerRepository.ListGroups
    Return _entities.GroupSet.ToList()
End Function

Public Function GetFirstGroup() As Group Implements IContactManagerRepository.GetFirstGroup
    Return _entities.GroupSet.Include("Contacts").FirstOrDefault()
End Function

Public Function GetGroup(ByVal id As Integer) As Group Implements IContactManagerRepository.GetGroup
    Return (From g In _entities.GroupSet.Include("Contacts") _
            Where g.Id = id _
            Select g).FirstOrDefault()
End Function

Public Sub DeleteGroup(ByVal groupToDelete As Group) Implements IContactManagerRepository.DeleteGroup
    Dim originalGroup = GetGroup(groupToDelete.Id)
    _entities.DeleteObject(originalGroup)
    _entities.SaveChanges()
End Sub

End Class

Vytvoření zobrazení

ASP.NET aplikaci MVC při použití výchozího modulu zobrazení ASP.NET. Zobrazení tedy nevytádáte v reakci na konkrétní test jednotek. Vzhledem k tomu, že aplikace by byla bez zobrazení nepoužitá, nemůžeme tuto iteraci dokončit bez vytvoření a úpravy zobrazení obsažených v aplikaci Contact Manager.

Potřebujeme vytvořit následující nová zobrazení pro správu skupin kontaktů (viz obrázek 7):

  • Views\Group\Index.aspx – zobrazí seznam skupin kontaktů.
  • Views\Group\Delete.aspx – zobrazí formulář potvrzení pro odstranění skupiny kontaktů.

Zobrazení Index skupiny

Obrázek 07: Zobrazení indexu skupiny (kliknutím zobrazíte obrázek v plné velikosti)

Musíme upravit následující existující zobrazení tak, aby zahrnovala skupiny kontaktů:

  • Views\Home\Create.aspx
  • Views\Home\Edit.aspx
  • Views\Home\Index.aspx

Upravená zobrazení si můžete prohlédnout v aplikaci sady Visual Studio, která doprovází tento kurz. Například obrázek 8 znázorňuje zobrazení Index kontaktů.

Zobrazení indexu kontaktů

Obrázek 08: Zobrazení indexu kontaktů (kliknutím zobrazíte obrázek v plné velikosti)

Souhrn

V této iteraci jsme do aplikace Contact Manager přidali nové funkce, a to pomocí metodologie návrhu aplikací řízeného testováním. Začali jsme vytvořením sady uživatelských scénářů. Vytvořili jsme sadu testů jednotek, která odpovídá požadavkům vyjádřeným uživatelským scénářem. Nakonec jsme napsali dostatek kódu, abychom splnili požadavky vyjádřené testy jednotek.

Po napsání dostatečného množství kódu pro splnění požadavků vyjádřených testy jednotek jsme aktualizovali naši databázi a zobrazení. Do naší databáze jsme přidali novou tabulku Groups a aktualizovali jsme datový model Entity Framework. Také jsme vytvořili a upravili sadu zobrazení.

V další iteraci – závěrečné iteraci – přepíšeme aplikaci tak, aby využívala ajax. Využitím ajaxu zlepšíme rychlost odezvy a výkon aplikace Contact Manager.