ASP.NET MVC v praxi od A do Z, 15. díl – Unit testy II. část
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

ASP.NET MVC v praxi od A do Z, 15. díl – Unit testy II. částASP.NET MVC v praxi od A do Z, 15. díl – Unit testy II. část

 

ASP.NET MVC v praxi od A do Z, 15. díl – Unit testy II. část

Google       Google       10. 11. 2009       18 017×

V posledním dílu tohoto seriálu doděláme podporu pro unit testy, vytvoříme falešnou DinnerRepository třídu a napíšeme si pár dalších testů.

Navážeme na minulý díl a upravíme DinnersController tak, aby podporoval rozhraní IDinnerRepository namísto třídy DinnerRepository.

Momentálně vypadá třída DinnersController takto:

public class DinnersController : Controller
{
    DinnerRepository dinnerRepository = new DinnerRepository();

Upravená verze bude vypadat následovně:

public class DinnersController : Controller
{
    IDinnerRepository dinnerRepository;

    public DinnersController() : this(new DinnerRepository())
    {}

    public DinnersController(IDinnerRepository repository)
    {
        dinnerRepository = repository;
    }

Jak jistě vidíte, přidali jsme do třídy DinnersController dva konstruktory – první použije naši současnou implementaci třídy DinnerRepository, druhý umožňuje předat nějakou jinou třídu odvozenou od rozhraní IDinnerRepository.

Vytvoření třídy FakeDinnerRepository

Začneme vytvořením nového adresáře pojmenovaného „Fakes“ v projektu NerdDinner.Tests a rovnou do něj přidáme novou třídu FakeDinnerRepository.

Nová třída bude implementovat rozhraní IDinnerRepository, ukážu vám už rovnou finální kód:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NerdDinner.Models;

namespace NerdDinner.Tests.Fakes
{
    class FakeDinnerRepository : IDinnerRepository
    {
        private List<Dinner> dinnerList;

        public FakeDinnerRepository(List<Dinner> dinners)
        {
            dinnerList = dinners;
        }

        public IQueryable<Dinner> FindAllDinners()
        {
            return dinnerList.AsQueryable();
        }

        public IQueryable<Dinner> FindUpcomingDinners()
        {
            return (from dinner in dinnerList
                    where dinner.EventDate > DateTime.Now
                    select dinner).AsQueryable();
        }

        public Dinner GetDinner(int id)
        {
            return dinnerList.SingleOrDefault(d => d.DinnerID == id);
        }

        public IQueryable<Dinner> FindByLocation(float latitude, float longitude)
        {
            return (from dinner in dinnerList
                    where dinner.Latitude == latitude && dinner.Longitude == longitude
                    select dinner).AsQueryable();

        }

        public void Add(Dinner dinner)
        {
            dinnerList.Add(dinner);
        }

        public void Delete(Dinner dinner)
        {
            dinnerList.Remove(dinner);
        }

        public void Save()
        {
            foreach (Dinner dinner in dinnerList)
            {
                if (!dinner.IsValid)
                    throw new ApplicationException("Porušení validačních pravidel");
            }
        }
    }
}

Výborně, teď máme třídu implementující IDinnerRepository, která nepotřebuje připojení k databázi, ale pracuje jen s objekty v paměti.

Použití třídy FakedinnerRepository v unit testech

V minulém dílu jsme si napsali unit testy, které neprošly kvůli připojení k databázi, teď už to můžeme snadno napravit:

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

namespace NerdDinner.Tests.Controllers
{
    [TestClass]
    public class DinnersControllerTest
    {
        List<Dinner> CreateTestDinners()
        {
            List<Dinner> dinners = new List<Dinner>();

            for (int i = 0; i < 101; i++)
            {
                Dinner sampleDinner = new Dinner()
                {
                    DinnerID = i,
                    Title = "Nějaká večeře",
                    HostedBy = "Někdo",
                    Address = "Nějaká adresa",
                    Country = "USA",
                    ContactPhone = "425-555-1212",
                    Description = "Nějaký popis",
                    EventDate = DateTime.Now.AddDays(i),
                    Latitude = 99,
                    Longitude = -99
                };
                dinners.Add(sampleDinner);
            }

            return dinners;
        }

        DinnersController CreateDinnersController()
        {
            var repository = new FakeDinnerRepository(CreateTestDinners());
            return new DinnersController(repository);
        }


        [TestMethod]
        public void DetailsAction_Should_Return_View_For_Dinner()
        {
            // Vytvoření pokusného objektu
            var controller = CreateDinnersController();

            // Úkon
            var result = controller.Details(1);

            // Rozhodnutí o výsledku testu
            Assert.IsInstanceOfType(result, typeof(ViewResult));
        }

        [TestMethod]
        public void DetailsAction_Should_Return_NotFoundView_For_BogusDinner()
        {
            // Vytvoření pokusného objektu
            var controller = CreateDinnersController();

            // Úkon
            var result = controller.Details(999) as ViewResult;

            // Rozhodnutí o výsledku testu
            Assert.AreEqual("NotFound", result.ViewName);
        }
    }
}

Nyní by měly oba testy proběhnout bez problémů. Máme teď v rukou neskutečně silný nástroj pro testování DinnersControlleru – můžeme testovat jakoukoliv část aplikace používající tento controller bez přístupu k databázi, vše tak trvá jen pár sekund (ale testy si pořád musíme napsat sami).

Unit testy akční metody Edit

Teď je na řadě akční metoda Edit, nejprve její HTTP-GET verze:

[Authorize]
public ActionResult Edit(int id)
{
    Dinner dinner = dinnerRepository.GetDinner(id);
    if (!dinner.IsHostedBy(User.Identity.Name))
        return View("InvalidOwner");

    return View(new DinnerFormViewModel(dinner));
}

Vytvoříme test, který ověřuje, zda je view vrácený objektem DinnerFormViewModel vyrenderován, pokud zažádáme o validní večeři:

[TestMethod]
public void EditAction_Should_Return_View_For_ValidDinner()
{

    // Vytvoření pokusného objektu
    var controller = CreateDinnersController();

    // Úkon
    var result = controller.Edit(1) as ViewResult;

    // Rozhodnutí o výsledku testu
    Assert.IsInstanceOfType(result.ViewData.Model, typeof(DinnerFormViewModel));
}

Tento test ale vrátí chybu. A to z poměrně jasného důvodu – dostaneme totiž výjimku null reference, když se metoda Edit pokusí přistoupit k vlastnosti User.Identity.Name pomocí metody Dinner.IsHostedBy().

Objekt User bázové třídy Controller zapouzdřuje detaily o přihlášeném uživateli. My ale testujeme DinnersController mimo webové prostředí a objektu User nejsou vůbec přiřazeny nějaké hodnoty (proto null reference). Proto si ho opět zfalšujeme.

Zfalšování vlastnosti User.Identity.Name

Za účelem vytváření „nepravých“ informací vznikly tzv. „mocking“ frameworky. Takový mocking framework nám může například dynamicky vytvářet objekt User, ze kterého pak budeme načítat uživatelské jméno.

Existuje hned několik mocking frameworků s podporou ASP.NET MVC, jejich výčet naleznete například zde. My využijeme framework nazvaný „Moq“, který stáhnete odsud.

Po stažení přidáme referenci na Moq.dll do projektu NerdDinner.Tests.

Teď nám už nic nebrání vytvořit pomocnou metodu, která přijme uživatelské jméno jako parametr a dosadí ho pomocí vlastnosti User.Identity.Name do instance DinnersControlleru:

// Nezapomeňte přidat direktivu using Moq; !
DinnersController CreateDinnersControllerAs(string userName)
{

    var mock = new Mock<ControllerContext>();
    mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(userName);
    mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);

    var controller = CreateDinnersController();
    controller.ControllerContext = mock.Object;

    return controller;
}

V kódu výše využíváme instanci třídy Mock pro vytvoření falešného ControllerContext objektu (to je objekt, který ASP.NET MVC předává všem Controller třídám a vystavuje objekty jako User, Request, Response a Session). Metodou SetupGet říkáme, že „userName“, který jsme předali metodě jako parametr, má být vrácen jako HttpContext.User.Identity.Name.

Podobným způsobem můžeme zfalšovat kolik vlastností a metod se nám jen zachce, například druhé volání metody SetupGet v kódu výše vrací „true“ pro autentifikaci uživatele.

Nyní můžeme vytvořit několik testů, které pro svou funkčnost vyžadují uživatelské jméno:

[TestMethod]
public void EditAction_Should_Return_EditView_When_ValidOwner()
{

    // Vytvoření pokusného objektu
    var controller = CreateDinnersControllerAs("Někdo");

    // Úkon
    var result = controller.Edit(1) as ViewResult;

    // Rozhodnutí o výsledku testu
    Assert.IsInstanceOfType(result.ViewData.Model, typeof(DinnerFormViewModel));
}

[TestMethod]
public void EditAction_Should_Return_InvalidOwnerView_When_InvalidOwner()
{

    // Vytvoření pokusného objektu
    var controller = CreateDinnersControllerAs("Ne_vlastník");

    // Úkon
    var result = controller.Edit(1) as ViewResult;

    // Rozhodnutí o výsledku testu
    Assert.AreEqual(result.ViewName, "InvalidOwner");
} 

Ještě nesmíte zapomenout přepsat v testu EditAction_Should_Return_View_For_ValidDinner volání metody „CreateDinnersController“ na novou „CreateDinnersControllerAs“, která využívá mockingu. Všechny testy už by měly proběhnout v pořádku.

Edit ve verzi HTTP-POST

Testování GET verze metody Edit máme za sebou, na závěr celého seriálu se podíváme na její POST verzi. To, co dělá tuto situaci zajímavou, je přítomnost volání UpdateModel v metodě Edit:

// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Edit(int id, FormCollection formValues)
{
    Dinner dinner = dinnerRepository.GetDinner(id);
    if (!dinner.IsHostedBy(User.Identity.Name))
        return View("InvalidOwner");
    try
    {
        UpdateModel(dinner);
        dinnerRepository.Save();

        return RedirectToAction("Details", new {id = dinner.DinnerID});
    }
    catch
    {
        ModelState.AddRuleViolations(dinner.GetRuleViolations());

        return View(new DinnerFormViewModel(dinner));
    }
}

Níže vidíte dva testy, které ukazují způsob, jak metodě UpdateModel přiřadit informace, které má použít. Používáme k tomu objekt FormCollection, který potřebnými daty naplníme a pak ho přiřadíme do vlastnosti ValueProvider na controlleru.

První test ověřuje, zda je při úspěšném uložení uživatel přesměrován na akční metodu „Details“. Druhý test zase zjišťuje, zda je při neplatném vstupu znovu zobrazen view a chybová zpráva:

[TestMethod]
public void EditAction_Should_Redirect_When_Update_Successful()
{
    // Vytvoření pokusného objektu     
    var controller = CreateDinnersControllerAs("Někdo");

    var formValues = new FormCollection()
                         {
                             {"Title", "blabla"},
                             {"Description", "Nějaký popis"}
                         };

    controller.ValueProvider = formValues.ToValueProvider();

    // Úkon
    var result = controller.Edit(1, formValues) as RedirectToRouteResult;

    // Rozhodnutí o výsledku testu
    Assert.AreEqual("Details", result.RouteValues["Action"]);
}

[TestMethod]
public void EditAction_Should_Redisplay_With_Errors_When_Update_Fails()
{
    // Vytvoření pokusného objektu
    var controller = CreateDinnersControllerAs("Někdo");

    var formValues = new FormCollection()
                         {
                             {"EventDate", "(tady by mělo být datum, místo toho je tu tenhle text)"}
                         };

    controller.ValueProvider = formValues.ToValueProvider();

    // Úkon
    var result = controller.Edit(1, formValues) as ViewResult;

    // Rozhodnutí o výsledku testu
    Assert.IsNotNull(result, "Očekáváno znovuzobrazení view");
    Assert.IsTrue(result.ViewData.ModelState.Count > 0, "Očekávány chyby");
}

Závěr

A máme to za sebou! Tím myslím skutečně vše, co jsem vám chtěl v tomto seriálu sdělit. Nezbývá mi než doufat, že jste se naučili mnoho nových věcí a budete se této zajímavé technologii věnovat i nadále. Těším se na viděnou u dalšího seriálu.

Zdroj: http://nerddinnerbook.s3.amazonaws.com/Part12.htm

×Odeslání článku na tvůj Kindle

Zadej svůj Kindle e-mail a my ti pošleme článek na tvůj Kindle.
Musíš mít povolený příjem obsahu do svého Kindle z naší e-mailové adresy kindle@programujte.com.

E-mailová adresa (např. novak@kindle.com):

TIP: Pokud chceš dostávat naše články každé ráno do svého Kindle, koukni do sekce Články do Kindle.

Hlasování bylo ukončeno    
0 hlasů
Google
Jakub studuje informatiku na FIT ČVUT, jeho oblíbenou platformou je .NET.
Web     Twitter     Facebook     LinkedIn    

Nové články

Obrázek ke článku Hybridní inteligentní systémy 2

Hybridní inteligentní systémy 2

V technické praxi využíváme často kombinaci různých disciplín umělé inteligence a klasických výpočtů. Takovým systémům říkáme hybridní systémy. V tomto článku se zmíním o určitém typu hybridního systému, který je užitečný ve velmi složitých výrobních procesech.

Obrázek ke článku Jak vést kvalitně tým v IT oboru: Naprogramujte si ty správné manažerské kvality

Jak vést kvalitně tým v IT oboru: Naprogramujte si ty správné manažerské kvality

Vedení týmu v oboru informačních technologií se nijak zvlášť neliší od jiných oborů. Přesto však IT manažeři čelí výzvě v podobě velmi rychlého rozvoje a tím i rostoucími nároky na své lidi. Udržet pozornost, motivaci a efektivitu týmu vyžaduje opravdu pevné manažerské základy a zároveň otevřenost a flexibilitu pro stále nové výzvy.

Obrázek ke článku Síla týmů se na home office může vytrácet. Odborníci radí, jak z pracovních omezení vytěžit maximum

Síla týmů se na home office může vytrácet. Odborníci radí, jak z pracovních omezení vytěžit maximum

Za poslední rok se podoba práce zaměstnanců změnila k nepoznání. Především plošné zavedení home office, které mělo být zpočátku jen dočasným opatřením, je pro mnohé už více než rok každodenní realitou. Co ale dělat, když se při práci z domova ztrácí motivace, zaměstnanci přestávají komunikovat a dříve fungující tým se rozpadá na skupinu solitérů? Odborníci na personalistiku dali dohromady několik rad, jak udržet tým v chodu, i když pracovní podmínky nejsou ideální.

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032024 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý