Sdílet prostřednictvím


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

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 proti testům jednotek napíšeme kód. V této iteraci přidáme skupiny kontaktů.

Vytvoření aplikace Správy kontaktů ASP.NET MVC (C#)

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

Aplikaci sestavíme pomocí několika iterací. S každou iterací aplikaci postupně vylepšujeme. Cílem tohoto přístupu s více iteracemi 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 základních databázových operací: vytvoření, čtení, aktualizace a odstranění (CRUD).

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

  • Iterace č. 3 – přidání ověření formuláře Ve třetí iteraci přidáme základní ověření formuláře. Bráníme uživatelům v odeslá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ě na párování. V této čtvrté iteraci využijeme několik vzorů návrhu softwaru, které usnadňují údržbu a úpravy aplikace Contact Manager. Například refaktorujeme aplikaci tak, aby používala vzor úložiště a model injektáže 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. Vysmíváme si třídy datového modelu a sestavujeme testy 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 proti testům jednotek napíšeme kód. 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 to, 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. Při nácviku vývoje řízeného testy nejprve napíšete testy a pak proti testům napíšete kód.

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. Psaní kódu, který projde testem jednotek (zelený)
  3. Refaktoring kódu (refaktoring)

Nejprve napíšete test jednotek. Test jednotek by měl vyjadřovat 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 test jednotek měl selhat. Test by měl selhat, protože jste ještě nenapsali žádný kód aplikace, který by splňoval test.

Dále napíšete jenom dostatečný počet kódu, aby test jednotek prošel. Cílem je napsat kód co nejlínějším, nejsklopenější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.

Procvičování vývoje řízeného testy přináší řadu výhod. Za prvé, vývoj řízený testy vás nutí zaměřit se na kód, který je skutečně 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, je vám zabráněno v bloudění do plevelů a psaní obrovského 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.

Vývoj řízený testy vás nakonec 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á vypadne z okna. Při cvičení vývoje řízeného testy je naopak pravděpodobnější, že budete při psaní testů jednotek užitečnější, protože vývoj řízený testy 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 například skupiny Business a Friend.

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

Co se testuje

Jak jsme probírali 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 nejprve na psaní kontroleru a obchodní logiky. Nedotýkáme se databáze ani zobrazení. Až do úplného konce tohoto kurzu nebudeme upravovat databázi ani vytvářet naše zobrazení. Začneme s tím, co se dá testovat.

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ě vyvstává otázka: 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ý scénář je velmi stručný (obvykle jedna věta) popis požadavků na software. Měl by to být netechnické popisy požadavku napsané z pohledu uživatele.

Tady je sada uživatelských scénářů, které popisují funkce vyžadované novou funkcí Skupina 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 při vytváření nového kontaktu vybrat skupinu kontaktů.
  5. Uživatel může vybrat skupinu kontaktů při úpravě 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 procesu vytváření aplikace může být sada uživatelských scénářů ještě přesnější. Uživatelský scénář můžete rozdělit na několik 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.cs 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.cs

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Tests.Controllers
{
    [TestClass]
    public class GroupControllerTest
    {

        [TestMethod]
        public void Index()
        {
            // Arrange
            var controller = new GroupController();

            // Act
            var result = (ViewResult)controller.Index();
        
            // Assert
            Assert.IsInstanceOfType(result.ViewData.Model, typeof(IEnumerable));
        }
    }
}

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

using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Controllers
{
    public class GroupController : Controller
    {
        public ActionResult Index()
        {
            var groups = new List();
            return View(groups);
        }

    }
}

Výpis 3 – Models\Group.cs

namespace ContactManager.Models
{
    public class Group
    {
    }
}

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

[TestMethod]
public void Create()
{
    // Arrange
    var controller = new GroupController();

    // Act
    var groupToCreate = new Group();
    controller.Create(groupToCreate);

    // Assert
    var result = (ViewResult)controller.Index();
    var groups = (IEnumerable<Group>)result.ViewData.Model;
    CollectionAssert.Contains(groups.ToList(), groupToCreate);
}

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

using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Models;
using System.Collections;

namespace ContactManager.Controllers
{
    public class GroupController : Controller
    {
        private IList<Group> _groups = new List<Group>();

        public ActionResult Index()
        {
            return View(_groups);
        }

        public ActionResult Create(Group groupToCreate)
        {
            _groups.Add(groupToCreate);
            return RedirectToAction("Index");
        }
    }
}

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

[TestMethod]
public void CreateRequiredName()
{
    // Arrange
    var controller = new GroupController();

    // Act
    var groupToCreate = new Group();
    groupToCreate.Name = String.Empty;
    var result = (ViewResult)controller.Create(groupToCreate);

    // Assert
    var error = result.ViewData.ModelState["Name"].Errors[0];
    Assert.AreEqual("Name is required.", error.ErrorMessage);
}

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 – Models\Group.cs

namespace ContactManager.Models
{
    public class Group
    {
        public string Name { get; set; }
    }
}

Výpis 8 – Controllers\GroupController.cs

public ActionResult Create(Group groupToCreate)
{
    // Validation logic
    if (groupToCreate.Name.Trim().Length == 0)
    {
        ModelState.AddModelError("Name", "Name is required.");
        return View("Create");
    }
    
    // Database logic
    _groups.Add(groupToCreate);
    return RedirectToAction("Index");
}

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

using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Controllers
{
    public class GroupController : Controller
    {

        private IContactManagerService _service;

        public GroupController()
        {
            _service = new ContactManagerService(new ModelStateWrapper(this.ModelState));
        }

        public GroupController(IContactManagerService service)
        {
            _service = service;
        }

        public ActionResult Index()
        {
            return View(_service.ListGroups());
        }

        public ActionResult Create(Group groupToCreate)
        {
            if (_service.CreateGroup(groupToCreate))
                return RedirectToAction("Index");
            return View("Create");
        }
    }
}

Výpis 10 – Controllers\ContactManagerService.cs

public bool ValidateGroup(Group groupToValidate)
{
    if (groupToValidate.Name.Trim().Length == 0)
       _validationDictionary.AddError("Name", "Name is required.");
    return _validationDictionary.IsValid;
}

public bool CreateGroup(Group groupToCreate)
{
    // Validation logic
    if (!ValidateGroup(groupToCreate))
        return false;

    // Database logic
    try
    {
        _repository.CreateGroup(groupToCreate);
    }
    catch
    {
        return false;
    }
    return true;
}

public IEnumerable<Group> ListGroups()
{
    return _repository.ListGroups();
}

Výpis 11 – Controllers\FakeContactManagerRepository.cs

using System;
using System.Collections.Generic;
using ContactManager.Models;

namespace ContactManager.Tests.Models
{
    public class FakeContactManagerRepository : IContactManagerRepository
    {
        private IList<Group> _groups = new List<Group>(); 
        
        #region IContactManagerRepository Members

        // Group methods

        public Group CreateGroup(Group groupToCreate)
        {
            _groups.Add(groupToCreate);
            return groupToCreate;
        }

        public IEnumerable<Group> ListGroups()
        {
            return _groups;
        }

        // Contact methods
        
        public Contact CreateContact(Contact contactToCreate)
        {
            throw new NotImplementedException();
        }

        public void DeleteContact(Contact contactToDelete)
        {
            throw new NotImplementedException();
        }

        public Contact EditContact(Contact contactToEdit)
        {
            throw new NotImplementedException();
        }

        public Contact GetContact(int id)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<Contact> ListContacts()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

Ú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 Group CreateGroup(Group groupToCreate)
{
    throw new NotImplementedException();
}

public IEnumerable<Group> ListGroups()
{
    throw new NotImplementedException();
}

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

using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Controllers;
using ContactManager.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections;
using System.Linq;
using System;
using ContactManager.Tests.Models;

namespace ContactManager.Tests.Controllers
{
    [TestClass]
    public class GroupControllerTest
    {
        private IContactManagerRepository _repository;
        private ModelStateDictionary _modelState;
        private IContactManagerService _service;

        [TestInitialize]
        public void Initialize()
        {
            _repository = new FakeContactManagerRepository();
            _modelState = new ModelStateDictionary();
            _service = new ContactManagerService(new ModelStateWrapper(_modelState), _repository);

        }

        [TestMethod]
        public void Index()
        {
            // Arrange
            var controller = new GroupController(_service);

            // Act
            var result = (ViewResult)controller.Index();
        
            // Assert
            Assert.IsInstanceOfType(result.ViewData.Model, typeof(IEnumerable));
        }

        [TestMethod]
        public void Create()
        {
            // Arrange
            var controller = new GroupController(_service);

            // Act
            var groupToCreate = new Group();
            groupToCreate.Name = "Business";
            controller.Create(groupToCreate);

            // Assert
            var result = (ViewResult)controller.Index();
            var groups = (IEnumerable)result.ViewData.Model;
            CollectionAssert.Contains(groups.ToList(), groupToCreate);
        }

        [TestMethod]
        public void CreateRequiredName()
        {
            // Arrange
            var controller = new GroupController(_service);

            // Act
            var groupToCreate = new Group();
            groupToCreate.Name = String.Empty;
            var result = (ViewResult)controller.Create(groupToCreate);

            // Assert
            var error = _modelState["Name"].Errors[0];
            Assert.AreEqual("Name is required.", error.ErrorMessage);
        }
    
    }
}

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. Uložte novou tabulku s názvem Skupiny kliknutím na ikonu diskety.

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 myši 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. Přidejte do tabulky Kontakty 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ích klíčů a jako sloupec primárního klíče Vyberte Id. Vyberte Kontakty jako tabulku cizího klíče a GroupId jako sloupec cizího klíče (viz obrázek 4). Klikněte na tlačítko OK.
  7. V části INSERT a UPDATE Specification (Specifikace aktualizace) vyberte hodnotu Cascade (Kaskádová ) pro delete rule (Kaskádové) pro odstranění pravidla.
  8. Kliknutím na tlačítko Zavřít zavřete dialogové okno Vztahy cizích klíčů.
  9. Kliknutím na tlačítko Uložit uložte změny do tabulky Kontakty.

Vytvoření relace mezi tabulkami databáze

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 Skupiny na Skupina (jednotné číslo).
  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 Skupina (jednotné číslo).

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. Entity Designer by měly zobrazovat obě entity (viz obrázek 6).

Designer entity zobrazující skupinu a kontakt

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

Vytvoř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, který vyhovuje našim testům jednotek. Konečná verze rozhraní IContactManagerRepository je obsažena ve výpisu 14.

Výpis 14 – Models\IContactManagerRepository.cs

using System.Collections.Generic;

namespace ContactManager.Models
{
    public interface IContactManagerRepository
    {
        // Contact methods
        Contact CreateContact(int groupId, Contact contactToCreate);
        void DeleteContact(Contact contactToDelete);
        Contact EditContact(int groupId, Contact contactToEdit);
        Contact GetContact(int id);

        // Group methods
        Group CreateGroup(Group groupToCreate);
        IEnumerable<Group> ListGroups();
        Group GetGroup(int groupId);
        Group GetFirstGroup();
        void DeleteGroup(Group groupToDelete);
    }
}

Ve skutečnosti jsme neimplementovali žádnou z metod souvisejících s prací se skupinami kontaktů. V současné době EntityContactManagerRepository třída obsahuje metody zástupných procedur pro každou skupinu kontaktů metody uvedené v rozhraní IContactManagerRepository. Například metoda ListGroups() aktuálně vypadá takto:

public IEnumerable<Group> ListGroups()
{
    throw new NotImplementedException();
}

Metody zástupných procedur 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 ve výpisu 13.

Výpis 13 – Models\EntityContactManagerRepository.cs

using System.Collections.Generic;
using System.Linq;
using System;

namespace ContactManager.Models
{
    public class EntityContactManagerRepository : ContactManager.Models.IContactManagerRepository
    {
        private ContactManagerDBEntities _entities = new ContactManagerDBEntities();

        // Contact methods

        public Contact GetContact(int id)
        {
            return (from c in _entities.ContactSet.Include("Group")
                    where c.Id == id
                    select c).FirstOrDefault();
        }

        public Contact CreateContact(int groupId, Contact contactToCreate)
        {
            // Associate group with contact
            contactToCreate.Group = GetGroup(groupId);

            // Save new contact
            _entities.AddToContactSet(contactToCreate);
            _entities.SaveChanges();
            return contactToCreate;
        }

        public Contact EditContact(int groupId, Contact contactToEdit)
        {
            // Get original contact
            var originalContact = GetContact(contactToEdit.Id);
            
            // Update with new group
            originalContact.Group = GetGroup(groupId);
            
            // Save changes
            _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit);
            _entities.SaveChanges();
            return contactToEdit;
        }

        public void DeleteContact(Contact contactToDelete)
        {
            var originalContact = GetContact(contactToDelete.Id);
            _entities.DeleteObject(originalContact);
            _entities.SaveChanges();
        }

        public Group CreateGroup(Group groupToCreate)
        {
            _entities.AddToGroupSet(groupToCreate);
            _entities.SaveChanges();
            return groupToCreate;
        }

        // Group Methods

        public IEnumerable<Group> ListGroups()
        {
            return _entities.GroupSet.ToList();
        }

        public Group GetFirstGroup()
        {
            return _entities.GroupSet.Include("Contacts").FirstOrDefault();
        }

        public Group GetGroup(int id)
        {
            return (from g in _entities.GroupSet.Include("Contacts")
                       where g.Id == id
                       select g).FirstOrDefault();
        }

        public void DeleteGroup(Group groupToDelete)
        {
            var originalGroup = GetGroup(groupToDelete.Id);
            _entities.DeleteObject(originalGroup);
            _entities.SaveChanges();

        }

    }
}

Vytváření zobrazení

ASP.NET aplikaci MVC při použití výchozího modulu ASP.NET zobrazení. Nevytovávejte tedy zobrazení v reakci na konkrétní test jednotek. Vzhledem k tomu, že aplikace by bez zobrazení nebyla k ničemu, 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í Index 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
  • Zobrazení\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í Index kontaktů

Obrázek 08: Zobrazení Index 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ými příběhy. Nakonec jsme napsali jen tolik 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 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 – konečné iteraci – přepíšeme aplikaci tak, aby využívala ajax. Když využijeme ajax, zlepšíme rychlost odezvy a výkon aplikace Contact Manager.