Iteracja 5 — Tworzenie testów jednostkowych (VB)

autor: Microsoft

Pobierz kod

W piątej iteracji ułatwiamy konserwację i modyfikowanie aplikacji przez dodawanie testów jednostkowych. Wyśmiewamy nasze klasy modelu danych i tworzymy testy jednostkowe dla naszych kontrolerów i logiki walidacji.

Tworzenie aplikacji MVC do zarządzania kontaktami ASP.NET (VB)

W tej serii samouczków utworzymy całą aplikację do zarządzania kontaktami od początku do końca. Aplikacja Contact Manager umożliwia przechowywanie informacji kontaktowych — nazw, numerów telefonów i adresów e-mail — dla listy osób.

Tworzymy aplikację za pośrednictwem wielu iteracji. Wraz z każdą iteracją stopniowo ulepszamy aplikację. Celem tego podejścia iteracji wielokrotnej jest umożliwienie zrozumienia przyczyny każdej zmiany.

  • Iteracja #1 — tworzenie aplikacji. W pierwszej iteracji tworzymy Menedżera kontaktów w najprostszy możliwy sposób. Dodajemy obsługę podstawowych operacji bazy danych: tworzenie, odczyt, aktualizowanie i usuwanie (CRUD).

  • Iteracja #2 — sprawia, że aplikacja wygląda ładnie. W tej iteracji poprawiamy wygląd aplikacji, modyfikując domyślną stronę wzorcową widoku MVC ASP.NET oraz kaskadowy arkusz stylów.

  • Iteracja 3 — dodawanie walidacji formularza. W trzeciej iteracji dodamy podstawową walidację formularza. Uniemożliwiamy użytkownikom przesyłanie formularza bez wypełniania wymaganych pól formularza. Weryfikujemy również adresy e-mail i numery telefonów.

  • Iteracja #4 — luźno sprzężenie aplikacji. W tej czwartej iteracji korzystamy z kilku wzorców projektowych oprogramowania, aby ułatwić konserwację i modyfikowanie aplikacji Contact Manager. Na przykład refaktoryzujemy naszą aplikację, aby używać wzorca repozytorium i wzorca wstrzykiwania zależności.

  • Iteracja 5 — tworzenie testów jednostkowych. W piątej iteracji ułatwiamy konserwację i modyfikowanie aplikacji przez dodawanie testów jednostkowych. Wyśmiewamy nasze klasy modelu danych i tworzymy testy jednostkowe dla naszych kontrolerów i logiki walidacji.

  • Iteracja 6 — używanie programowania opartego na testach. W tej szóstej iteracji do naszej aplikacji dodamy nowe funkcje, pisząc najpierw testy jednostkowe i pisząc kod względem testów jednostkowych. W tej iteracji dodajemy grupy kontaktów.

  • Iteracja #7 — dodawanie funkcji Ajax. W siódmej iteracji poprawiamy czas odpowiedzi i wydajność naszej aplikacji, dodając obsługę Ajax.

Ta iteracja

W poprzedniej iteracji aplikacji Contact Manager refaktoryzowaliśmy aplikację, aby była luźniej sprzężona. Aplikacja została rozdzielona na odrębne warstwy kontrolera, usługi i repozytorium. Każda warstwa współdziała z warstwą pod nią za pośrednictwem interfejsów.

Refaktoryzowaliśmy aplikację, aby ułatwić konserwację i modyfikowanie aplikacji. Jeśli na przykład musimy użyć nowej technologii dostępu do danych, możemy po prostu zmienić warstwę repozytorium bez dotykania kontrolera lub warstwy usługi. Dzięki luźnej połączeniu menedżera kontaktów wprowadziliśmy aplikację bardziej odporną na zmiany.

Ale co się stanie, gdy musimy dodać nową funkcję do aplikacji Contact Manager? A co się stanie, gdy naprawimy usterkę? Smutna, ale dobrze sprawdzona prawda pisania kodu polega na tym, że za każdym razem, gdy dotkniesz kodu, stwarzasz ryzyko wprowadzenia nowych usterek.

Na przykład pewnego dnia menedżer może poprosić Cię o dodanie nowej funkcji do Menedżera kontaktów. Chce dodać obsługę grup kontaktów. Chce, aby umożliwić użytkownikom organizowanie swoich kontaktów w grupach, takich jak Przyjaciele, Biznes itd.

Aby zaimplementować tę nową funkcję, należy zmodyfikować wszystkie trzy warstwy aplikacji Contact Manager. Należy dodać nowe funkcje do kontrolerów, warstwy usługi i repozytorium. Zaraz po rozpoczęciu modyfikowania kodu ryzykujesz niezgodność funkcji, które wcześniej działały.

Refaktoryzacja aplikacji na oddzielne warstwy, tak jak w poprzedniej iteracji, była dobrą rzeczą. To było dobre, ponieważ umożliwia nam wprowadzanie zmian w całych warstwach bez dotykania reszty aplikacji. Jeśli jednak chcesz ułatwić konserwację i modyfikowanie kodu w obrębie warstwy, należy utworzyć testy jednostkowe dla kodu.

Test jednostkowy służy do testowania pojedynczej jednostki kodu. Te jednostki kodu są mniejsze niż całe warstwy aplikacji. Zazwyczaj test jednostkowy służy do sprawdzania, czy określona metoda w kodzie zachowuje się w oczekiwany sposób. Można na przykład utworzyć test jednostkowy dla metody CreateContact() uwidocznionej przez klasę ContactManagerService.

Testy jednostkowe aplikacji działają tak samo jak sieć bezpieczeństwa. Za każdym razem, gdy modyfikujesz kod w aplikacji, możesz uruchomić zestaw testów jednostkowych, aby sprawdzić, czy modyfikacja przerywa istniejące funkcje. Testy jednostkowe umożliwiają bezpieczne modyfikowanie kodu. Testy jednostkowe sprawiają, że cały kod w aplikacji jest bardziej odporny na zmiany.

W tej iteracji dodajemy testy jednostkowe do naszej aplikacji Contact Manager. Dzięki następnej iteracji możemy dodać grupy kontaktów do naszej aplikacji bez obaw o przerywanie istniejących funkcji.

Uwaga

Istnieją różne struktury testowania jednostkowego, w tym NUnit, xUnit.net i MbUnit. W tym samouczku używamy platformy testów jednostkowych zawartych w programie Visual Studio. Można jednak równie łatwo użyć jednej z tych alternatywnych struktur.

Co jest testowane

W idealnym świecie cały kod będzie objęty testami jednostkowym. W idealnym świecie miałbyś idealną siatkę bezpieczeństwa. Możesz zmodyfikować dowolny wiersz kodu w aplikacji i natychmiast wiedzieć, wykonując testy jednostkowe, czy zmiana przerwała istniejącą funkcjonalność.

Jednak nie żyjemy w idealnym świecie. W praktyce podczas pisania testów jednostkowych koncentrujesz się na pisaniu testów dla logiki biznesowej (na przykład logiki weryfikacji). W szczególności nie należy pisać testów jednostkowych dla logiki dostępu do danych ani logiki widoku.

Aby być przydatnym, testy jednostkowe muszą być wykonywane bardzo szybko. Można łatwo gromadzić setki (a nawet tysiące) testów jednostkowych dla aplikacji. Jeśli testy jednostkowe trwają długo, należy unikać ich wykonywania. Innymi słowy, długotrwałe testy jednostkowe są bezużyteczne do codziennych celów kodowania.

Z tego powodu zwykle nie są zapisywane testy jednostkowe kodu, który wchodzi w interakcję z bazą danych. Uruchamianie setek testów jednostkowych dla aktywnej bazy danych byłoby zbyt powolne. Zamiast tego wyśmiewasz bazę danych i piszesz kod, który współdziała z pozorną bazą danych (omawiamy pozorowanie poniższej bazy danych).

Z podobnej przyczyny zazwyczaj nie są zapisywane testy jednostkowe dla widoków. Aby przetestować widok, należy uruchomić serwer internetowy. Ponieważ tworzenie serwera internetowego jest stosunkowo powolnym procesem, tworzenie testów jednostkowych dla widoków nie jest zalecane.

Jeśli widok zawiera skomplikowaną logikę, rozważ przeniesienie logiki do metod pomocnika. Testy jednostkowe dla metod pomocnika, które są wykonywane bez uruchamiania serwera internetowego.

Uwaga

Podczas pisania testów logiki dostępu do danych lub logiki wyświetlania nie jest dobrym pomysłem podczas pisania testów jednostkowych, te testy mogą być bardzo przydatne podczas kompilowania testów funkcjonalnych lub integracji.

Uwaga

ASP.NET MVC jest aparatem Web Forms View. Aparat widoków Web Forms jest zależny od serwera internetowego, ale inne aparaty widoków mogą nie być.

Używanie makiety struktury obiektów

Podczas kompilowania testów jednostkowych prawie zawsze trzeba korzystać z platformy Mock Object. Platforma Mock Object umożliwia tworzenie makietów i wycinków dla klas w aplikacji.

Na przykład można użyć platformy Mock Object, aby wygenerować pozorną wersję klasy repozytorium. W ten sposób można użyć pozornej klasy repozytorium zamiast rzeczywistej klasy repozytorium w testach jednostkowych. Użycie makiety repozytorium umożliwia uniknięcie wykonywania kodu bazy danych podczas wykonywania testu jednostkowego.

Program Visual Studio nie zawiera platformy Mock Object. Istnieje jednak kilka komercyjnych i open source platform obiektów mock dostępnych dla platformy .NET Framework:

  1. Moq — ta struktura jest dostępna w ramach licencji usługi BSD open source. Możesz pobrać aplikację Moq z witryny https://code.google.com/p/moq/.
  2. Makiety nosorożca — ta struktura jest dostępna w ramach licencji BSD open source. Możesz pobrać makiety Nosorożca z witryny http://ayende.com/projects/rhino-mocks.aspx.
  3. Typemock Isolator — jest to platforma komercyjna. Możesz pobrać wersję próbną z witryny http://www.typemock.com/.

W tym samouczku postanowiłem użyć Moq. Można jednak równie łatwo użyć makiety Nosorożca lub Isolatora Typumock, aby utworzyć obiekty makiety dla aplikacji Contact Manager.

Przed rozpoczęciem korzystania z narzędzia Moq należy wykonać następujące czynności:

  1. .
  2. Przed rozpakowywaniem pobierania upewnij się, że kliknij go prawym przyciskiem myszy i kliknij przycisk z etykietą Odblokuj (zobacz Rysunek 1).
  3. Rozpakuj pobieranie.
  4. Dodaj odwołanie do zestawu Moq do projektu Test, wybierając opcję menu Projekt, Dodaj odwołanie, aby otworzyć okno dialogowe Dodawanie odwołania . Na karcie Przeglądaj przejdź do folderu, w którym rozpakujesz aplikację Moq i wybierz zestaw Moq.dll. Kliknij przycisk OK (zobacz Rysunek 2).

Odblokowywanie Moq

Rysunek 01. Odblokowywanie aplikacji Moq(Kliknij, aby wyświetlić obraz pełnowymiarowy)

Odwołania po dodaniu moq

Rysunek 02. Odwołania po dodaniu pliku Moq(Kliknij, aby wyświetlić obraz pełnowymiarowy)

Tworzenie testów jednostkowych dla warstwy usługi

Zacznijmy od utworzenia zestawu testów jednostkowych dla warstwy usługi aplikacji Contact Manager. Użyjemy tych testów do zweryfikowania logiki walidacji.

Utwórz nowy folder o nazwie Models w projekcie ContactManager.Tests. Następnie kliknij prawym przyciskiem myszy folder Modele i wybierz polecenie Dodaj, Nowy test. Zostanie wyświetlone okno dialogowe Dodawanie nowego testu pokazane na rysunku 3. Wybierz szablon Unit Test i nadaj nowemu testowi nazwę ContactManagerServiceTest.vb. Kliknij przycisk OK , aby dodać nowy test do projektu testowego.

Uwaga

Ogólnie rzecz biorąc, chcesz, aby struktura folderów projektu testowego odpowiadała strukturze folderów projektu ASP.NET MVC. Na przykład testy kontrolera są umieszczane w folderze Kontrolery, testy modelu w folderze Models itd.

Models\ContactManagerServiceTest.cs

Rysunek 03. Models\ContactManagerServiceTest.cs(Kliknij, aby wyświetlić obraz pełnowymiarowy)

Początkowo chcemy przetestować metodę CreateContact() uwidacznianą przez klasę ContactManagerService. Utworzymy następujące pięć testów:

  • CreateContact() — testy, które createContact() zwracają wartość true, gdy prawidłowy kontakt jest przekazywany do metody .
  • CreateContactRequiredFirstName() — sprawdza, czy komunikat o błędzie jest dodawany do stanu modelu, gdy kontakt z brakującą nazwą zostanie przekazany do metody CreateContact().
  • CreateContactRequiredLastName() — sprawdza, czy komunikat o błędzie jest dodawany do stanu modelu, gdy kontakt z brakującą nazwą jest przekazywany do metody CreateContact().
  • CreateContactInvalidPhone() — sprawdza, czy komunikat o błędzie jest dodawany do stanu modelu, gdy kontakt z nieprawidłowym numerem telefonu jest przekazywany do metody CreateContact().
  • CreateContactInvalidEmail() — sprawdza, czy komunikat o błędzie jest dodawany do stanu modelu, gdy kontakt z nieprawidłowym adresem e-mail jest przekazywany do metody CreateContact().

Pierwszy test sprawdza, czy prawidłowy kontakt nie generuje błędu walidacji. Pozostałe testy sprawdzają każdą z reguł walidacji.

Kod dla tych testów znajduje się na liście 1.

Lista 1 — Models\ContactManagerServiceTest.vb

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

<TestClass()> _
Public Class ContactManagerServiceTest

    Private _mockRepository As Mock(Of IContactManagerRepository)
    Private _modelState As ModelStateDictionary
    Private _service As IContactManagerService

    <TestInitialize()> _
    Public Sub Initialize()
        _mockRepository = New Mock(Of IContactManagerRepository)()
        _modelState = New ModelStateDictionary()
        _service = New ContactManagerService(new ModelStateWrapper(_modelState), _mockRepository.Object)
    End Sub

    <TestMethod()> _
    Public Sub CreateContact()
        ' Arrange
        Dim contactToCreate = Contact.CreateContact(-1, "Stephen", "Walther", "555-5555", "steve@somewhere.com")

        ' Act
        Dim result = _service.CreateContact(contactToCreate)

        ' Assert
        Assert.IsTrue(result)
    End Sub

    <TestMethod()> _
    Public Sub CreateContactRequiredFirstName()
        ' Arrange
        Dim contactToCreate = Contact.CreateContact(-1, String.Empty, "Walther", "555-5555", "steve@somewhere.com")

        ' Act
        Dim result = _service.CreateContact(contactToCreate)

        ' Assert
        Assert.IsFalse(result)
        Dim [error] = _modelState("FirstName").Errors(0)
        Assert.AreEqual("First name is required.", [error].ErrorMessage)
    End Sub

    <TestMethod()> _
    Public Sub CreateContactRequiredLastName()
        ' Arrange
        Dim contactToCreate = Contact.CreateContact(-1, "Stephen", String.Empty, "555-5555", "steve@somewhere.com")

        ' Act
        Dim result = _service.CreateContact(contactToCreate)

        ' Assert
        Assert.IsFalse(result)
        Dim [error] = _modelState("LastName").Errors(0)
        Assert.AreEqual("Last name is required.", [error].ErrorMessage)
    End Sub

    <TestMethod()> _
    Public Sub CreateContactInvalidPhone()
        ' Arrange
        Dim contactToCreate = Contact.CreateContact(-1, "Stephen", "Walther", "apple", "steve@somewhere.com")

        ' Act
        Dim result = _service.CreateContact(contactToCreate)

        ' Assert
        Assert.IsFalse(result)
        Dim [error] = _modelState("Phone").Errors(0)
        Assert.AreEqual("Invalid phone number.", [error].ErrorMessage)
    End Sub

    <TestMethod()> _
    Public Sub CreateContactInvalidEmail()
        ' Arrange
        Dim contactToCreate = Contact.CreateContact(-1, "Stephen", "Walther", "555-5555", "apple")

        ' Act
        Dim result = _service.CreateContact(contactToCreate)

        ' Assert
        Assert.IsFalse(result)
        Dim [error] = _modelState("Email").Errors(0)
        Assert.AreEqual("Invalid email address.", [error].ErrorMessage)
    End Sub
End Class

Ponieważ używamy klasy Contact w liście 1, musimy dodać odwołanie do programu Microsoft Entity Framework do naszego projektu Test. Dodaj odwołanie do zestawu System.Data.Entity.

Lista 1 zawiera metodę o nazwie Initialize(), która jest ozdobiona atrybutem [TestInitialize]. Ta metoda jest wywoływana automatycznie przed uruchomieniem każdego z testów jednostkowych (jest wywoływana 5 razy przed każdym testem jednostkowym). Metoda Initialize() tworzy pozorne repozytorium z następującym wierszem kodu:

_mockRepository = New Mock(Of IContactManagerRepository)()

Ten wiersz kodu używa struktury Moq do generowania makiety repozytorium z interfejsu IContactManagerRepository. Repozytorium makiety jest używane zamiast rzeczywistego repozytorium EntityContactManager, aby uniknąć uzyskiwania dostępu do bazy danych po uruchomieniu każdego testu jednostkowego. Makiety repozytorium implementuje metody interfejsu IContactManagerRepository, ale metody w rzeczywistości nie robią nic.

Uwaga

W przypadku korzystania z platformy Moq istnieje rozróżnienie między _mockRepository a _mockRepository.Object. Poprzedni odwołuje się do klasy Mock(Of IContactManagerRepository), która zawiera metody określania sposobu zachowania makiety repozytorium. Ten ostatni odnosi się do rzeczywistego makiety repozytorium, które implementuje interfejs IContactManagerRepository.

Repozytorium makiety jest używane w metodzie Initialize() podczas tworzenia wystąpienia klasy ContactManagerService. Wszystkie poszczególne testy jednostkowe używają tego wystąpienia klasy ContactManagerService.

Lista 1 zawiera pięć metod odpowiadających każdej z testów jednostkowych. Każda z tych metod jest ozdobiona atrybutem [TestMethod]. Po uruchomieniu testów jednostkowych wywoływana jest każda metoda, która ma ten atrybut. Innymi słowy, każda metoda ozdobiona atrybutem [TestMethod] jest testem jednostkowym.

Pierwszy test jednostkowy o nazwie CreateContact() sprawdza, czy wywołanie metody CreateContact() zwraca wartość true, gdy prawidłowe wystąpienie klasy Contact zostanie przekazane do metody. Test tworzy wystąpienie klasy Contact, wywołuje metodę CreateContact() i sprawdza, czy funkcja CreateContact() zwraca wartość true.

Pozostałe testy sprawdzają, czy gdy metoda CreateContact() jest wywoływana z nieprawidłowym kontaktem, metoda zwraca wartość false, a oczekiwany komunikat o błędzie weryfikacji jest dodawany do stanu modelu. Na przykład test CreateContactRequiredFirstName() tworzy wystąpienie klasy Contact z pustym ciągiem dla właściwości FirstName. Następnie metoda CreateContact() jest wywoływana z nieprawidłowym kontaktem. Na koniec test sprawdza, czy funkcja CreateContact() zwraca wartość false, a stan modelu zawiera oczekiwany komunikat o błędzie weryfikacji "Imię jest wymagane".

Testy jednostkowe można uruchomić na liście 1, wybierając opcję menu Test, Uruchom, Wszystkie testy w rozwiązaniu (CTRL+R, A). Wyniki testów są wyświetlane w oknie Wyniki testu (patrz Rysunek 4).

Wyniki testu

Rysunek 04. Wyniki testu (kliknij, aby wyświetlić obraz pełnowymiarowy)

Tworzenie testów jednostkowych dla kontrolerów

ASP.NET aplikacja MVC kontroluje przepływ interakcji użytkownika. Podczas testowania kontrolera chcesz sprawdzić, czy kontroler zwraca odpowiedni wynik akcji i wyświetla dane. Możesz również sprawdzić, czy kontroler wchodzi w interakcje z klasami modeli w oczekiwany sposób.

Na przykład lista 2 zawiera dwa testy jednostkowe metody Contact controller Create(). Pierwszy test jednostkowy sprawdza, czy po przekazaniu prawidłowego kontaktu do metody Create() metoda Create() przekierowuje do akcji Indeks. Innymi słowy, gdy przekazano prawidłowy kontakt, metoda Create() powinna zwrócić akcję RedirectToRouteResult reprezentującą akcję Indeks.

Nie chcemy testować warstwy usługi ContactManager podczas testowania warstwy kontrolera. W związku z tym wyśmiewamy warstwę usługi za pomocą następującego kodu w metodzie Initialize:

_service = New Mock(Of IContactManagerService)()

W teście jednostkowym CreateValidContact() wyśmiewamy zachowanie wywoływania metody CreateContact() warstwy usługi z następującym wierszem kodu:

_service.Expect( Function(s) s.CreateContact(contactToCreate) ).Returns(True)

Ten wiersz kodu powoduje, że pozorna usługa ContactManager zwraca wartość true po wywołaniu metody CreateContact(). Wyśmiewając warstwę usługi, możemy przetestować zachowanie kontrolera bez konieczności wykonywania kodu w warstwie usługi.

Drugi test jednostkowy sprawdza, czy akcja Create() zwraca widok Utwórz po przekazaniu nieprawidłowego kontaktu do metody . Powodujemy, że metoda CreateContact() warstwy usługi zwraca wartość false z następującym wierszem kodu:

_service.Expect( Function(s) s.CreateContact(contactToCreate) ).Returns(False)

Jeśli metoda Create() zachowuje się zgodnie z oczekiwaniami, powinna zwrócić widok Utwórz, gdy warstwa usługi zwraca wartość false. W ten sposób kontroler może wyświetlić komunikaty o błędach weryfikacji w widoku Tworzenie, a użytkownik ma szansę naprawić nieprawidłowe właściwości kontaktu.

Jeśli planujesz kompilowanie testów jednostkowych dla kontrolerów, należy zwrócić jawne nazwy widoków z akcji kontrolera. Na przykład nie zwracaj widoku w następujący sposób:

Widok powrotny()

Zamiast tego zwróć widok podobny do następującego:

Widok powrotu ("Utwórz")

Jeśli nie jesteś jawny podczas zwracania widoku, właściwość ViewResult.ViewName zwraca pusty ciąg.

Lista 2 — Controllers\ContactControllerTest.vb

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

<TestClass()> _
Public Class ContactControllerTest

    Private _service As Mock(Of IContactManagerService)

    <TestInitialize()> _
    Public Sub Initialize()
        _service = New Mock(Of IContactManagerService)()
    End Sub

    <TestMethod()> _
    Public Sub CreateValidContact()
        ' Arrange
        Dim contactToCreate = New Contact()
        _service.Expect(Function(s) s.CreateContact(contactToCreate)).Returns(True)
        Dim controller = New ContactController(_service.Object)

        ' Act
        Dim result = CType(controller.Create(contactToCreate), RedirectToRouteResult)

        ' Assert
        Assert.AreEqual("Index", result.RouteValues("action"))
    End Sub

    <TestMethod()> _
    Public Sub CreateInvalidContact()
        ' Arrange
        Dim contactToCreate = New Contact()
        _service.Expect(Function(s) s.CreateContact(contactToCreate)).Returns(False)
        Dim controller = New ContactController(_service.Object)

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

        ' Assert
        Assert.AreEqual("Create", result.ViewName)
    End Sub

End Class

Podsumowanie

W tej iteracji utworzyliśmy testy jednostkowe dla naszej aplikacji Contact Manager. Możemy uruchomić te testy jednostkowe w dowolnym momencie, aby sprawdzić, czy nasza aplikacja nadal zachowuje się w oczekiwany sposób. Testy jednostkowe działają jako sieć bezpieczeństwa dla naszej aplikacji, umożliwiając nam bezpieczne modyfikowanie naszej aplikacji w przyszłości.

Utworzyliśmy dwa zestawy testów jednostkowych. Najpierw przetestowaliśmy naszą logikę walidacji, tworząc testy jednostkowe dla naszej warstwy usług. Następnie przetestowaliśmy logikę sterowania przepływem, tworząc testy jednostkowe dla warstwy kontrolera. Podczas testowania warstwy usługi izolowaliśmy nasze testy dla naszej warstwy usługi od warstwy repozytorium przez wyśmiewanie warstwy repozytorium. Podczas testowania warstwy kontrolera izolowaliśmy nasze testy dla warstwy kontrolera przez wyśmiewanie warstwy usługi.

W następnej iteracji zmodyfikujemy aplikację Contact Manager tak, aby obsługiwała grupy kontaktów. Dodamy tę nową funkcjonalność do naszej aplikacji przy użyciu procesu projektowania oprogramowania nazywanego programowaniem opartym na testach.