Iterace č. 6 – použití vývoje řízeného testy (VB)
od Microsoftu
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):
- Zápis testu jednotek, který selže (červený)
- Napište kód, který projde testem jednotek (zelená)
- 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ů:
- Uživatel může zobrazit seznam skupin kontaktů.
- Uživatel může vytvořit novou skupinu kontaktů.
- Uživatel může odstranit existující skupinu kontaktů.
- Uživatel může vybrat skupinu kontaktů při vytváření nového kontaktu.
- Uživatel může vybrat skupinu kontaktů při úpravách existujícího kontaktu.
- V zobrazení Index se zobrazí seznam skupin kontaktů.
- 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 .
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.
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:
- 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.
- Zadejte dva sloupce popsané níže v tabulce Designer.
- Označte sloupec Id jako primární klíč a sloupec Identita.
- 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:
- Klikněte pravým tlačítkem na tabulku Kontakty a vyberte možnost nabídky Zobrazit data tabulky.
- 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:
- Poklikáním na tabulku Kontakty v okně Průzkumník serveru otevřete Designer tabulky.
- Do tabulky Kontakty přidejte nový celočíselný sloupec s názvem GroupId.
- Kliknutím na tlačítko Relace otevřete dialogové okno Relace cizích klíčů (viz Obrázek 3).
- Klikněte na tlačítko Přidat.
- Klikněte na tlačítko se třemi tečky, které se zobrazí vedle tlačítka Specifikace tabulky a sloupců.
- 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.
- V části INSERT a UPDATE Specification (Specifikace vložení a aktualizace) vyberte hodnotu Cascade (Kaskáda) pro Delete Rule (Odstranit pravidlo).
- Kliknutím na tlačítko Zavřít zavřete dialogové okno Relace cizích klíčů.
- Kliknutím na tlačítko Uložit uložte změny do tabulky Kontakty.
Obrázek 03: Vytvoření relace databázové tabulky (kliknutím zobrazíte obrázek v plné velikosti)
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:
- Poklikáním na soubor ContactManagerModel.edmx ve složce Models otevřete Designer Entity.
- Klikněte pravým tlačítkem na plochu Designer a vyberte možnost nabídky Aktualizovat model z databáze.
- V Průvodci aktualizací vyberte tabulku Skupiny a klikněte na tlačítko Dokončit (viz Obrázek 5).
- 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í).
- 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í).
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).
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ů.
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ů.
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.