Iteracja 6 — Korzystanie z projektowania opartego na testach (VB)
autor: Microsoft
W tej szóstej iteracji dodamy nową funkcjonalność do naszej aplikacji, pisząc najpierw testy jednostkowe i pisząc kod względem testów jednostkowych. W tej iteracji dodajemy grupy kontaktów.
Tworzenie ASP.NET aplikacji MVC do zarządzania kontaktami (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. Po każdej iteracji stopniowo ulepszamy aplikację. Celem tego podejścia iteracji wielokrotnej jest umożliwienie zrozumienia przyczyny każdej zmiany.
Iteracja #1 — tworzenie aplikacji. W pierwszej iteracji utworzymy menedżera kontaktów w najprostszy możliwy sposób. Dodamy obsługę podstawowych operacji bazy danych: tworzenie, odczytywanie, 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ą ASP.NET widoku MVC i 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 połącz aplikację. W tej czwartej iteracji korzystamy z kilku wzorców projektowania 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 dodanie testów jednostkowych. Wyśmiewamy nasze klasy modelu danych i kompilujemy testy jednostkowe dla naszych kontrolerów i logiki walidacji.
Iteracja nr 6 — korzystanie z programowania opartego na testach. W tej szóstej iteracji dodamy nową funkcjonalność do naszej aplikacji, 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 reakcji i wydajność naszej aplikacji, dodając obsługę Ajax.
Ta iteracja
W poprzedniej iteracji aplikacji Contact Manager utworzyliśmy testy jednostkowe zapewniające sieć bezpieczeństwa dla naszego kodu. Motywacją do utworzenia testów jednostkowych było zwiększenie odporności naszego kodu na zmianę. Dzięki testom jednostkowym możemy szczęśliwie wprowadzić wszelkie zmiany w naszym kodzie i natychmiast wiedzieć, czy uszkodziliśmy istniejące funkcje.
W tej iteracji używamy testów jednostkowych do zupełnie innego celu. W tej iteracji używamy testów jednostkowych w ramach filozofii projektowania aplikacji nazywanej programowaniem opartym na testach. Podczas opracowywania opartego na testach najpierw pisać testy, a następnie pisać kod względem testów.
Dokładniej mówiąc, podczas trenowania programowania opartego na testach podczas tworzenia kodu (Red/Green/Refactor):
- Pisanie testu jednostkowego, który kończy się niepowodzeniem (czerwony)
- Pisanie kodu, który przechodzi test jednostkowy (Zielony)
- Refaktoryzacja kodu (refaktoryzacja)
Najpierw należy napisać test jednostkowy. Test jednostkowy powinien wyrazić zamiar zachowania kodu. Podczas pierwszego tworzenia testu jednostkowego test jednostkowy powinien zakończyć się niepowodzeniem. Test powinien zakończyć się niepowodzeniem, ponieważ nie został jeszcze napisany żaden kod aplikacji, który spełnia test.
Następnie napiszesz wystarczająco dużo kodu, aby test jednostkowy przeszedł. Celem jest napisanie kodu w leniwy, niechlujny i najszybszy możliwy sposób. Nie należy tracić czasu na myślenie o architekturze aplikacji. Zamiast tego należy skupić się na pisaniu minimalnej ilości kodu niezbędnego do spełnienia intencji wyrażonej przez test jednostkowy.
Na koniec, po napisaniu wystarczającej ilości kodu, możesz cofnąć się i rozważyć ogólną architekturę aplikacji. W tym kroku napiszesz ponownie (refaktoryzację) kodu, korzystając z wzorców projektowania oprogramowania — takich jak wzorzec repozytorium — aby kod był bardziej konserwowalny. Możesz bez obaw ponownie napisać kod w tym kroku, ponieważ kod jest objęty testami jednostkowym.
Istnieje wiele korzyści, które wynikają z praktykowania programowania opartego na testach. Najpierw programowanie oparte na testach wymusza skupienie się na kodzie, który rzeczywiście musi być napisany. Ponieważ nieustannie koncentrujesz się na pisaniu wystarczającej ilości kodu, aby przejść określony test, nie można wędrować do chwastów i pisać ogromne ilości kodu, których nigdy nie będziesz używać.
Po drugie, metodologia projektowania "test pierwszy" wymusza pisanie kodu z perspektywy sposobu użycia kodu. Innymi słowy, podczas ćwiczeń programowania opartego na testach stale piszesz testy z perspektywy użytkownika. W związku z tym programowanie oparte na testach może spowodować czystsze i bardziej zrozumiałe interfejsy API.
Na koniec programowanie oparte na testach wymusza pisanie testów jednostkowych w ramach normalnego procesu pisania aplikacji. W miarę zbliżania się terminu projektu testowanie jest zazwyczaj pierwszą rzeczą, która wychodzi przez okno. W przypadku praktykowania programowania opartego na testach z drugiej strony bardziej prawdopodobne jest, aby być bardziej cnotliwym w pisaniu testów jednostkowych, ponieważ programowanie oparte na testach sprawia, że testy jednostkowe są centralnym elementem procesu tworzenia aplikacji.
Uwaga
Aby dowiedzieć się więcej na temat programowania opartego na testach, zalecam przeczytanie książki Michael Feathers Working Effectively with Legacy Code (Praca skutecznie z starszym kodem).
W tej iteracji dodamy nową funkcję do naszej aplikacji Contact Manager. Dodamy obsługę grup kontaktów. Za pomocą grup kontaktów można organizować kontakty w kategorie, takie jak grupy Biznesowe i Znajome.
Dodamy tę nową funkcjonalność do naszej aplikacji, wykonując proces programowania opartego na testach. Najpierw napiszemy nasze testy jednostkowe i napiszemy cały nasz kod względem tych testów.
Co jest testowane
Jak wspomniano w poprzedniej iteracji, zwykle nie są zapisywane testy jednostkowe logiki dostępu do danych ani logiki wyświetlania. Nie piszesz testów jednostkowych dla logiki dostępu do danych, ponieważ uzyskiwanie dostępu do bazy danych jest stosunkowo powolną operacją. Nie można pisać testów jednostkowych dla logiki widoku, ponieważ uzyskiwanie dostępu do widoku wymaga uruchomienia serwera internetowego, który jest stosunkowo powolnym działaniem. Nie należy pisać testu jednostkowego, chyba że test można wykonać ponownie bardzo szybko
Ponieważ programowanie oparte na testach jest oparte na testach jednostkowych, koncentrujemy się początkowo na pisaniu kontrolera i logiki biznesowej. Unikamy dotykania bazy danych lub widoków. Nie zmodyfikujemy bazy danych ani nie utworzymy naszych widoków do momentu zakończenia tego samouczka. Zaczynamy od tego, co można przetestować.
Tworzenie historii użytkowników
Podczas trenowania programowania opartego na testach zawsze zaczynasz od pisania testu. Natychmiast rodzi to pytanie: Jak zdecydować, jaki test należy napisać jako pierwszy? Aby odpowiedzieć na to pytanie, należy napisać zestaw historii użytkowników.
Historia użytkownika to bardzo krótki (zwykle jeden z zdań) opis wymagania dotyczącego oprogramowania. Powinien to być nieobsługiwny opis wymagania napisanego z perspektywy użytkownika.
Oto zestaw scenariuszy użytkowników opisujących funkcje wymagane przez nową funkcję grupy kontaktów:
- Użytkownik może wyświetlić listę grup kontaktów.
- Użytkownik może utworzyć nową grupę kontaktów.
- Użytkownik może usunąć istniejącą grupę kontaktów.
- Użytkownik może wybrać grupę kontaktów podczas tworzenia nowego kontaktu.
- Użytkownik może wybrać grupę kontaktów podczas edytowania istniejącego kontaktu.
- Lista grup kontaktów jest wyświetlana w widoku Indeks.
- Gdy użytkownik kliknie grupę kontaktów, zostanie wyświetlona lista pasujących kontaktów.
Zwróć uwagę, że ta lista historii użytkowników jest całkowicie zrozumiała dla klienta. Nie ma wzmianki o szczegółach implementacji technicznej.
Podczas tworzenia aplikacji zestaw scenariuszy użytkowników może stać się bardziej wyrafinowany. Możesz podzielić historię użytkownika na wiele scenariuszy (wymagania). Możesz na przykład zdecydować, że utworzenie nowej grupy kontaktów powinno obejmować walidację. Przesyłanie grupy kontaktów bez nazwy powinno zwrócić błąd weryfikacji.
Po utworzeniu listy scenariuszy użytkownika możesz przystąpić do pisania pierwszego testu jednostkowego. Zaczniemy od utworzenia testu jednostkowego na potrzeby wyświetlania listy grup kontaktów.
Wyświetlanie listy grup kontaktów
Nasza pierwsza historia użytkownika polega na tym, że użytkownik powinien mieć możliwość wyświetlania listy grup kontaktów. Musimy wyrazić tę historię przy użyciu testu.
Utwórz nowy test jednostkowy, klikając prawym przyciskiem myszy folder Controllers w projekcie ContactManager.Tests, wybierając pozycję Dodaj, Nowy test i wybierając szablon Testu jednostkowego (zobacz Rysunek 1). Nazwij nowy test jednostkowy GroupControllerTest.vb i kliknij przycisk OK .
Rysunek 01. Dodawanie testu jednostkowego GroupControllerTest (Kliknij, aby wyświetlić obraz pełnowymiarowy)
Nasz pierwszy test jednostkowy znajduje się na liście 1. Ten test sprawdza, czy metoda Index() kontrolera grupy zwraca zestaw grup. Test sprawdza, czy kolekcja grup jest zwracana w widoku danych.
Lista 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
Podczas pierwszego wpisywania kodu w programie Listing 1 w programie Visual Studio otrzymasz wiele czerwonych falochłych wierszy. Nie utworzyliśmy klas GroupController ani Group.
W tym momencie nie możemy nawet skompilować naszej aplikacji, abyśmy nie mogli wykonać pierwszego testu jednostkowego. To dobre. To liczy się jako test zakończony niepowodzeniem. W związku z tym mamy teraz uprawnienia do rozpoczęcia pisania kodu aplikacji. Musimy napisać wystarczająco dużo kodu, aby wykonać nasz test.
Klasa kontrolera grupy na liście 2 zawiera minimum kodu wymaganego do wykonania testu jednostkowego. Akcja Index() zwraca statycznie zakodowaną listę grup (klasa Grupa jest zdefiniowana w liście 3).
Lista 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
Lista 3 — Models\Group.vb
Public Class Group
End Class
Po dodaniu klas GroupController i Group do naszego projektu nasz pierwszy test jednostkowy zakończy się pomyślnie (zobacz Rysunek 2). Wykonaliśmy minimalną pracę wymaganą do wykonania testu. Nadszedł czas, aby świętować.
Rysunek 02. Sukces! (Kliknij, aby wyświetlić obraz w pełnym rozmiarze)
Tworzenie grup kontaktów
Teraz możemy przejść do drugiego scenariusza użytkownika. Musimy mieć możliwość tworzenia nowych grup kontaktów. Musimy wyrazić tę intencję za pomocą testu.
Test w liście Lista 4 sprawdza, czy wywołanie metody Create() z nową grupą dodaje grupę do listy grup zwracanych przez metodę Index(). Innymi słowy, jeśli utworzym nową grupę, powinienem mieć możliwość przywrócenia nowej grupy z listy grup zwróconych przez metodę Index().
Lista 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 w aplikacji Listing 4 wywołuje metodę Create() kontrolera grupy z nową grupą kontaktów. Następnie test sprawdza, czy wywołanie metody Index() kontrolera grupy zwraca nową grupę w widoku danych.
Zmodyfikowany kontroler grupy na liście 5 zawiera minimalne zmiany wymagane do wykonania nowego testu.
Lista 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 grupy na liście 5 ma nową akcję Create(). Ta akcja powoduje dodanie grupy do kolekcji grup. Zwróć uwagę, że akcja Index() została zmodyfikowana w celu zwrócenia zawartości kolekcji grup.
Po raz kolejny wykonaliśmy minimalną ilość pracy wymaganą do wykonania testu jednostkowego. Po wprowadzeniu tych zmian do kontrolera grupy wszystkie nasze testy jednostkowe przechodzą pomyślnie.
Dodawanie walidacji
To wymaganie nie zostało jawnie określone w scenariuszu użytkownika. Istnieje jednak uzasadnione wymaganie, aby grupa miała nazwę. W przeciwnym razie organizowanie kontaktów w grupy nie byłoby bardzo przydatne.
Lista 6 zawiera nowy test, który wyraża tę intencję. Ten test sprawdza, czy próba utworzenia grupy bez podawania nazwy powoduje wyświetlenie komunikatu o błędzie weryfikacji w stanie modelu.
Lista 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
Aby spełnić ten test, musimy dodać właściwość Name do klasy Group (zobacz Lista 7). Ponadto musimy dodać niewielką logikę weryfikacji do akcji Create() kontrolera grupy (zobacz Lista 8).
Lista 7 — Models\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
Lista 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
Zwróć uwagę, że akcja Utwórz() kontrolera grupy zawiera teraz zarówno walidację, jak i logikę bazy danych. Obecnie baza danych używana przez kontroler grupy składa się z niczego więcej niż kolekcji w pamięci.
Czas refaktoryzacji
Trzecim krokiem w obszarze Red/Green/Refactor jest część Refaktoryzacja. W tym momencie musimy wrócić z naszego kodu i zastanowić się, jak możemy refaktoryzować naszą aplikację, aby ulepszyć jej projekt. Etap refaktoryzacji to etap, na którym trudno jest myśleć o najlepszym sposobie implementowania zasad i wzorców projektowania oprogramowania.
Możemy zmodyfikować nasz kod w dowolny sposób, aby ulepszyć projektowanie kodu. Mamy siatkę bezpieczeństwa testów jednostkowych, które uniemożliwiają nam przerwanie istniejących funkcji.
W tej chwili kontroler grupy jest bałaganem z perspektywy dobrego projektowania oprogramowania. Kontroler grupy zawiera splątane bałagan weryfikacji i kodu dostępu do danych. Aby uniknąć naruszenia zasady o pojedynczej odpowiedzialności, musimy podzielić te obawy na różne klasy.
Nasza refaktoryzowana klasa kontrolera grupy znajduje się na liście 9. Kontroler został zmodyfikowany w celu korzystania z warstwy usługi ContactManager. Jest to ta sama warstwa usługi, która jest używana z kontrolerem kontaktów.
Lista 10 zawiera nowe metody dodane do warstwy usługi ContactManager w celu obsługi walidacji, wyświetlania listy i tworzenia grup. Interfejs IContactManagerService został zaktualizowany w celu uwzględnienia nowych metod.
Lista 11 zawiera nową klasę FakeContactManagerRepository, która implementuje interfejs IContactManagerRepository. W przeciwieństwie do klasy EntityContactManagerRepository, która implementuje również interfejs IContactManagerRepository, nasza nowa klasa FakeContactManagerRepository nie komunikuje się z bazą danych. Klasa FakeContactManagerRepository używa kolekcji w pamięci jako serwera proxy dla bazy danych. Użyjemy tej klasy w naszych testach jednostkowych jako fałszywej warstwy repozytorium.
Lista 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
Lista 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
Lista 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
Zmodyfikowanie interfejsu IContactManagerRepository wymaga użycia metody CreateGroup() i ListGroups() w klasie EntityContactManagerRepository. Najbardziej leniwym i najszybszym sposobem na to jest dodanie metod wycinków, które wyglądają następująco:
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
Na koniec te zmiany w projekcie aplikacji wymagają wprowadzenia pewnych modyfikacji w naszych testach jednostkowych. Teraz musimy użyć repozytorium FakeContactManager podczas wykonywania testów jednostkowych. Zaktualizowana klasa GroupControllerTest znajduje się na liście 12.
Lista 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
Po wprowadzeniu wszystkich tych zmian ponownie wszystkie nasze testy jednostkowe przechodzą pomyślnie. Ukończyliśmy cały cykl refaktoryzacji Red/Green/Refactor. Zaimplementowaliśmy dwa pierwsze scenariusze użytkownika. Mamy teraz obsługę testów jednostkowych dla wymagań wyrażonych w scenariuszach użytkownika. Zaimplementowanie pozostałej części scenariuszy użytkownika obejmuje powtórzenie tego samego cyklu Red/Green/Refactor.
Modyfikowanie bazy danych
Niestety, mimo że spełniliśmy wszystkie wymagania wyrażone przez nasze testy jednostkowe, nasza praca nie jest wykonywana. Nadal musimy zmodyfikować naszą bazę danych.
Musimy utworzyć nową tabelę bazy danych grupy. Wykonaj następujące kroki:
- W oknie Eksplorator serwera kliknij prawym przyciskiem myszy folder Tables i wybierz opcję menu Dodaj nową tabelę.
- Wprowadź dwie kolumny opisane poniżej w Projektant tabeli.
- Oznacz kolumnę Id jako klucz podstawowy i kolumnę Tożsamość.
- Zapisz nową tabelę o nazwie Grupy, klikając ikonę dyskietki.
Nazwa kolumny | Typ danych | Zezwalaj na wartości null |
---|---|---|
Id | int | Fałsz |
Nazwa | nvarchar(50) | Fałsz |
Następnie musimy usunąć wszystkie dane z tabeli Contacts (w przeciwnym razie nie będziemy mogli utworzyć relacji między tabelami Kontakty i Grupy). Wykonaj następujące kroki:
- Kliknij prawym przyciskiem myszy tabelę Kontakty i wybierz opcję menu Pokaż dane tabeli.
- Usuń wszystkie wiersze.
Następnie musimy zdefiniować relację między tabelą bazy danych Grupy a istniejącą tabelą bazy danych Kontakty. Wykonaj następujące kroki:
- Kliknij dwukrotnie tabelę Kontakty w oknie Eksplorator serwera, aby otworzyć Projektant tabeli.
- Dodaj nową kolumnę całkowitą do tabeli Kontakty o nazwie GroupId.
- Kliknij przycisk Relacja, aby otworzyć okno dialogowe Relacje kluczy obcych (zobacz Rysunek 3).
- Kliknij przycisk Dodaj.
- Kliknij przycisk wielokropka, który zostanie wyświetlony obok przycisku Specyfikacja tabeli i kolumn.
- W oknie dialogowym Tabele i kolumny wybierz pozycję Grupy jako tabelę klucza podstawowego i identyfikator jako kolumnę klucza podstawowego. Wybierz pozycję Kontakty jako tabelę kluczy obcych i GroupId jako kolumnę klucza obcego (zobacz Rysunek 4). Kliknij przycisk OK.
- W obszarze INSERT and UPDATE Specification (Specyfikacja wstawiania i aktualizacji) wybierz wartość Cascade for Delete Rule (Kaskada usuwania reguły).
- Kliknij przycisk Zamknij, aby zamknąć okno dialogowe Relacje kluczy obcych.
- Kliknij przycisk Zapisz, aby zapisać zmiany w tabeli Kontakty.
Rysunek 03. Tworzenie relacji tabeli bazy danych (kliknij, aby wyświetlić obraz o pełnym rozmiarze)
Rysunek 04. Określanie relacji tabeli (kliknij, aby wyświetlić obraz pełnowymiarowy)
Aktualizowanie naszego modelu danych
Następnie musimy zaktualizować nasz model danych, aby reprezentować nową tabelę bazy danych. Wykonaj następujące kroki:
- Kliknij dwukrotnie plik ContactManagerModel.edmx w folderze Models, aby otworzyć Projektant jednostki.
- Kliknij prawym przyciskiem myszy powierzchnię Projektant i wybierz opcję menu Aktualizuj model z bazy danych.
- W Kreatorze aktualizacji wybierz tabelę Grupy i kliknij przycisk Zakończ (zobacz Rysunek 5).
- Kliknij prawym przyciskiem myszy jednostkę Grupy i wybierz opcję menu Zmień nazwę. Zmień nazwę jednostki Grupy na Grupowanie (pojedyncza).
- Kliknij prawym przyciskiem myszy właściwość nawigacji Grupy, która jest wyświetlana w dolnej części jednostki Contact. Zmień nazwę właściwości nawigacji Grupy na Grupowanie (pojedyncza).
Rysunek 05. Aktualizowanie modelu programu Entity Framework z bazy danych (kliknij, aby wyświetlić obraz w pełnym rozmiarze)
Po wykonaniu tych kroków model danych będzie reprezentować tabele Kontakty i Grupy. Jednostka Projektant powinna zawierać obie jednostki (zobacz Rysunek 6).
Rysunek 06. Jednostka Projektant wyświetlania pozycji Grupa i Kontakt(Kliknij, aby wyświetlić obraz w pełnym rozmiarze)
Tworzenie klas repozytorium
Następnie musimy zaimplementować naszą klasę repozytorium. W trakcie tej iteracji dodaliśmy kilka nowych metod do interfejsu IContactManagerRepository podczas pisania kodu spełniającego nasze testy jednostkowe. Ostateczna wersja interfejsu IContactManagerRepository jest zawarta na liście 14.
Lista 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
W rzeczywistości nie zaimplementowaliśmy żadnej z metod związanych z pracą z grupami kontaktów w rzeczywistej klasie EntityContactManagerRepository. Obecnie klasa EntityContactManagerRepository zawiera metody wycinka dla każdej z metod grupy kontaktów wymienionych w interfejsie IContactManagerRepository. Na przykład metoda ListGroups() wygląda obecnie następująco:
Public Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerRepository.ListGroups
throw New NotImplementedException()
End Function
Metody wycinka umożliwiły kompilowanie aplikacji i przekazywanie testów jednostkowych. Jednak teraz nadszedł czas, aby rzeczywiście zaimplementować te metody. Ostateczna wersja klasy EntityContactManagerRepository znajduje się w liście 13.
Lista 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
Tworzenie widoków
ASP.NET aplikacji MVC podczas korzystania z domyślnego aparatu wyświetlania ASP.NET. W związku z tym nie tworzy się widoków w odpowiedzi na określony test jednostkowy. Jednak ponieważ aplikacja byłaby bezużyteczna bez widoków, nie można ukończyć tej iteracji bez tworzenia i modyfikowania widoków zawartych w aplikacji Contact Manager.
Musimy utworzyć następujące nowe widoki do zarządzania grupami kontaktów (zobacz Rysunek 7):
- Views\Group\Index.aspx — wyświetla listę grup kontaktów
- Views\Group\Delete.aspx — wyświetla formularz potwierdzenia usuwania grupy kontaktów
Rysunek 07. Widok indeksu grupy (kliknij, aby wyświetlić obraz pełnowymiarowy)
Musimy zmodyfikować następujące istniejące widoki, aby obejmowały grupy kontaktów:
- Views\Home\Create.aspx
- Views\Home\Edit.aspx
- Views\Home\Index.aspx
Zmodyfikowane widoki można wyświetlić, przeglądając aplikację programu Visual Studio, która towarzyszy temu samouczkowi. Na przykład rysunek 8 przedstawia widok Indeks kontaktów.
Rysunek 08. Widok indeksu kontaktu (kliknij, aby wyświetlić obraz pełnowymiarowy)
Podsumowanie
W tej iteracji dodaliśmy nowe funkcje do naszej aplikacji Contact Manager, postępując zgodnie z metodologią projektowania aplikacji programistycznych opartą na testach. Zaczęliśmy od utworzenia zestawu scenariuszy użytkownika. Utworzyliśmy zestaw testów jednostkowych odpowiadający wymaganiom wyrażonym przez scenariusze użytkownika. Na koniec napisaliśmy wystarczająco dużo kodu, aby spełnić wymagania wyrażone przez testy jednostkowe.
Po zakończeniu pisania wystarczającej ilości kodu, aby spełnić wymagania wyrażone przez testy jednostkowe, zaktualizowaliśmy bazę danych i widoki. Dodaliśmy nową tabelę Grupy do bazy danych i zaktualizowaliśmy model danych programu Entity Framework. Utworzyliśmy również i zmodyfikowaliśmy zestaw widoków.
W następnej iteracji — ostatniej iteracji — ponownie napiszemy aplikację, aby skorzystać z technologii Ajax. Korzystając z technologii Ajax, poprawimy czas odpowiedzi i wydajność aplikacji Contact Manager.
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla