Condividi tramite


Iterazione 6 - Usare lo sviluppo basato su test (VB)

di Microsoft

Scaricare il codice

In questa sesta iterazione si aggiungono nuove funzionalità all'applicazione scrivendo prima unit test e scrivendo codice per gli unit test. In questa iterazione si aggiungono gruppi di contatti.

Compilazione di un'applicazione MVC ASP.NET Gestione contatti (VB)

In questa serie di esercitazioni viene creata un'intera applicazione Contact Management dall'inizio alla fine. L'applicazione Contact Manager consente di archiviare le informazioni di contatto, ovvero nomi, numeri di telefono e indirizzi di posta elettronica, per un elenco di persone.

L'applicazione viene compilata su più iterazioni. Con ogni iterazione, l'applicazione viene migliorata gradualmente. L'obiettivo di questo approccio a più iterazioni è consentire di comprendere il motivo di ogni modifica.

  • Iterazione n. 1: creare l'applicazione. Nella prima iterazione viene creato il Contact Manager nel modo più semplice possibile. Viene aggiunto il supporto per le operazioni di base del database: Create, Read, Update e Delete (CRUD).

  • Iterazione n. 2: rendere l'applicazione più bella. In questa iterazione viene migliorata l'aspetto dell'applicazione modificando la pagina master della visualizzazione MVC predefinita ASP.NET e il foglio di stile css.

  • Iterazione n. 3: aggiungere la convalida dei moduli. Nella terza iterazione viene aggiunta la convalida dei moduli di base. Microsoft impedisce agli utenti di inviare un modulo senza completare i campi modulo obbligatori. Vengono convalidati anche gli indirizzi di posta elettronica e i numeri di telefono.

  • Iterazione n. 4: rendere l'applicazione ad accoppiamento libero. In questa quarta iterazione si sfruttano diversi modelli di progettazione software per semplificare la gestione e la modifica dell'applicazione Contact Manager. Ad esempio, si esegue il refactoring dell'applicazione per usare il modello repository e il modello di inserimento delle dipendenze.

  • Iterazione n. 5: creare unit test. Nella quinta iterazione l'applicazione risulta più semplice da gestire e modificare aggiungendo unit test. Si simulano le classi del modello di dati e si compilano unit test per i controller e la logica di convalida.

  • Iterazione n. 6: usare lo sviluppo basato su test. In questa sesta iterazione si aggiungono nuove funzionalità all'applicazione scrivendo prima unit test e scrivendo codice per gli unit test. In questa iterazione si aggiungono gruppi di contatti.

  • Iterazione n. 7: aggiungere la funzionalità Ajax. Nella settima iterazione viene migliorata la velocità di risposta e le prestazioni dell'applicazione aggiungendo il supporto per Ajax.

Iterazione

Nell'iterazione precedente dell'applicazione Contact Manager sono stati creati unit test per fornire una rete di sicurezza per il codice. La motivazione per la creazione degli unit test è stata quella di rendere il codice più resiliente alla modifica. Con gli unit test sul posto, è possibile apportare qualsiasi modifica al codice e sapere immediatamente se sono state interrotte le funzionalità esistenti.

In questa iterazione vengono usati unit test per uno scopo completamente diverso. In questa iterazione si usano unit test come parte di una filosofia di progettazione dell'applicazione denominata sviluppo basato su test. Quando si pratica lo sviluppo basato su test, si scrivono prima i test e quindi si scrive codice sui test.

Più precisamente, quando si pratica lo sviluppo basato su test, sono necessari tre passaggi durante la creazione di codice (Red/ Green/Refactoring):

  1. Scrivere uno unit test che ha esito negativo (rosso)
  2. Scrivere codice che supera lo unit test (Verde)
  3. Effettuare il refactoring del codice (refactoring)

Prima di tutto, si scrive lo unit test. Lo unit test deve esprimere l'intenzione di come si prevede che il codice si comporti. Quando si crea lo unit test per la prima volta, lo unit test dovrebbe non riuscire. Il test non riesce perché non è ancora stato scritto codice dell'applicazione che soddisfa il test.

Successivamente, si scrive codice sufficiente per il superamento dello unit test. L'obiettivo è scrivere il codice nel modo più pigrizio, più sloppiest e più veloce possibile. Non è consigliabile perdere tempo pensando all'architettura dell'applicazione. È invece consigliabile concentrarsi sulla scrittura della quantità minima di codice necessaria per soddisfare l'intenzione espressa dallo unit test.

Infine, dopo aver scritto codice sufficiente, è possibile tornare indietro e prendere in considerazione l'architettura complessiva dell'applicazione. In questo passaggio si riscrive (refactoring) il codice sfruttando i modelli di progettazione software, ad esempio il modello di repository, in modo che il codice sia più gestibile. È possibile riscrivere il codice in questo passaggio senza paura perché il codice è coperto dagli unit test.

Esistono molti vantaggi derivanti dalla pratica dello sviluppo basato su test. Prima di tutto, lo sviluppo basato su test impone di concentrarsi sul codice che deve effettivamente essere scritto. Poiché ci si concentra costantemente sulla scrittura di codice sufficiente per superare un determinato test, non è possibile spostarsi nelle weeds e scrivere enormi quantità di codice che non verranno mai usate.

In secondo luogo, una metodologia di progettazione "test first" impone di scrivere codice dal punto di vista del modo in cui verrà usato il codice. In altre parole, quando si pratica lo sviluppo basato su test, si scrivono costantemente i test dal punto di vista dell'utente. Di conseguenza, lo sviluppo basato su test può comportare API più pulite e comprensibili.

Infine, lo sviluppo basato su test forza la scrittura di unit test come parte del normale processo di scrittura di un'applicazione. Quando una scadenza del progetto si avvicina, il test è in genere la prima cosa che esce dalla finestra. Quando si pratica lo sviluppo basato su test, invece, è più probabile che si sia più virtuosi nella scrittura di unit test perché lo sviluppo basato su test rende gli unit test fondamentali per il processo di compilazione di un'applicazione.

Nota

Per altre informazioni sullo sviluppo basato sui test, è consigliabile leggere il libro Michael Feathers Working Effectively with Legacy Code .To learn more about test-driven development, I recommend that you read Michael Feathers book Working Effectively with Legacy Code.

In questa iterazione viene aggiunta una nuova funzionalità all'applicazione Contact Manager. Viene aggiunto il supporto per i gruppi di contatti. È possibile usare i gruppi di contatti per organizzare i contatti in categorie come i gruppi Business e Friend.

Questa nuova funzionalità verrà aggiunta all'applicazione seguendo un processo di sviluppo basato su test. Si scriveranno prima gli unit test e si scriverà tutto il codice in questi test.

Che cosa viene testato

Come illustrato nell'iterazione precedente, in genere non si scrivono unit test per la logica di accesso ai dati o la logica di visualizzazione. Non si scrivono unit test per la logica di accesso ai dati perché l'accesso a un database è un'operazione relativamente lenta. Non si scrivono unit test per la logica di visualizzazione perché l'accesso a una visualizzazione richiede l'esecuzione di un server Web che è un'operazione relativamente lenta. Non è consigliabile scrivere uno unit test a meno che il test non possa essere eseguito più volte e più volte molto velocemente

Poiché lo sviluppo basato su test è basato sugli unit test, ci concentriamo inizialmente sulla scrittura di controller e logica di business. Si evita di toccare il database o le viste. Il database non verrà modificato o creato fino alla fine di questa esercitazione. Iniziamo con ciò che è possibile testare.

Creazione di storie utente

Quando si pratica lo sviluppo basato su test, si inizia sempre scrivendo un test. Questo genera immediatamente la domanda: Come si decide quale test scrivere per primo? Per rispondere a questa domanda, è necessario scrivere un set di storie utente.

Una storia utente è una descrizione molto breve (in genere una frase) di un requisito software. Deve essere una descrizione non tecnica di un requisito scritto dal punto di vista dell'utente.

Ecco il set di storie utente che descrivono le funzionalità richieste dalla nuova funzionalità del gruppo di contatti:

  1. L'utente può visualizzare un elenco di gruppi di contatti.
  2. L'utente può creare un nuovo gruppo di contatti.
  3. L'utente può eliminare un gruppo di contatti esistente.
  4. L'utente può selezionare un gruppo di contatti durante la creazione di un nuovo contatto.
  5. L'utente può selezionare un gruppo di contatti durante la modifica di un contatto esistente.
  6. Nella visualizzazione Indice viene visualizzato un elenco di gruppi di contatti.
  7. Quando un utente fa clic su un gruppo di contatti, viene visualizzato un elenco di contatti corrispondenti.

Si noti che questo elenco di storie utente è completamente comprensibile da un cliente. Non vi è alcuna menzione dei dettagli tecnici dell'implementazione.

Durante il processo di compilazione dell'applicazione, il set di storie utente può diventare più raffinato. È possibile suddividere una storia utente in più storie (requisiti). Ad esempio, è possibile decidere che la creazione di un nuovo gruppo di contatti deve comportare la convalida. L'invio di un gruppo di contatti senza un nome deve restituire un errore di convalida.

Dopo aver creato un elenco di storie utente, è possibile scrivere il primo unit test. Si inizierà creando uno unit test per visualizzare l'elenco dei gruppi di contatti.

Elenco dei gruppi di contatti

La prima storia utente è che un utente deve essere in grado di visualizzare un elenco di gruppi di contatti. Dobbiamo esprimere questa storia con un test.

Creare un nuovo unit test facendo clic con il pulsante destro del mouse sulla cartella Controller nel progetto ContactManager.Tests, scegliendo Aggiungi, Nuovo test e selezionando il modello unit test (vedere la figura 1). Assegnare al nuovo unit test il nome GroupControllerTest.vb e fare clic sul pulsante OK .

Aggiunta dello unit test GroupControllerTest

Figura 01: Aggiunta dello unit test GroupControllerTest (fare clic per visualizzare l'immagine a dimensione intera)

Il primo unit test è contenuto nell'elenco 1. Questo test verifica che il metodo Index() del controller Group restituisca un set di gruppi. Il test verifica che venga restituita una raccolta di gruppi nei dati di visualizzazione.

Elenco 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

Quando si digita per la prima volta il codice in Listato 1 in Visual Studio, si otterranno molte righe ondulate rosse. Le classi GroupController o Group non sono state create.

A questo punto, non è nemmeno possibile compilare l'applicazione in modo che non sia possibile eseguire il primo unit test. È buono. Questo conteggia come test non superato. Di conseguenza, è ora disponibile l'autorizzazione per iniziare a scrivere il codice dell'applicazione. È necessario scrivere codice sufficiente per eseguire il test.

La classe controller Group nell'elenco 2 contiene il minimo di codice necessario per superare lo unit test. L'azione Index() restituisce un elenco codificato in modo statico di Gruppi (la classe Group è definita nell'elenco 3).

Elenco 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

Elenco 3 - Models\Group.vb

Public Class Group

End Class

Dopo aver aggiunto le classi GroupController e Group al progetto, il primo unit test viene completato correttamente (vedere la figura 2). Abbiamo svolto il lavoro minimo necessario per superare il test. È il momento di festeggiare.

Successo!

Figura 02: Operazione riuscita (Fare clic per visualizzare l'immagine a dimensione intera)

Creazione di gruppi di contatti

A questo punto è possibile passare alla seconda storia utente. Dobbiamo essere in grado di creare nuovi gruppi di contatti. Dobbiamo esprimere questa intenzione con un test.

Il test nell'elenco 4 verifica che la chiamata al metodo Create() con un nuovo gruppo aggiaggichi il gruppo all'elenco dei gruppi restituiti dal metodo Index(). In altre parole, se si crea un nuovo gruppo, si dovrebbe essere in grado di recuperare il nuovo gruppo dall'elenco di gruppi restituiti dal metodo Index().

Elenco 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

Il test nell'elenco 4 chiama il metodo Group controller Create() con un nuovo gruppo di contatti. Successivamente, il test verifica che la chiamata al metodo Group controller Index() restituisca il nuovo gruppo nei dati di visualizzazione.

Il controller di gruppo modificato nell'elenco 5 contiene il minimo di modifiche necessarie per superare il nuovo test.

Elenco 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

Il controller di gruppo nell'elenco 5 ha una nuova azione Create(). Questa azione aggiunge un oggetto Group a una raccolta di gruppi. Si noti che l'azione Index() è stata modificata per restituire il contenuto dell'insieme Groups.

Anche in questo caso è stata eseguita la quantità minima di lavoro necessaria per superare lo unit test. Dopo aver apportato queste modifiche al controller di gruppo, tutti gli unit test superano.

Aggiunta della convalida

Questo requisito non è stato dichiarato in modo esplicito nella storia utente. Tuttavia, è ragionevole richiedere che un gruppo abbia un nome. In caso contrario, l'organizzazione dei contatti in gruppi non sarebbe molto utile.

L'elenco 6 contiene un nuovo test che esprime questa intenzione. Questo test verifica che il tentativo di creare un gruppo senza fornire un nome restituisca un messaggio di errore di convalida nello stato del modello.

Elenco 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

Per soddisfare questo test, è necessario aggiungere una proprietà Name alla classe Group (vedere Listato 7). Inoltre, è necessario aggiungere un piccolo bit di logica di convalida all'azione Create() del controller di gruppo (vedere Listato 8).

Elenco 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

Elenco 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

Si noti che l'azione Group controller Create() contiene ora sia la convalida che la logica del database. Attualmente, il database usato dal controller di gruppo è costituito da una raccolta in memoria.

Tempo di refactoring

Il terzo passaggio in Red/Green/Refactor è la parte refactoring. A questo punto, è necessario tornare indietro dal codice e valutare come è possibile effettuare il refactoring dell'applicazione per migliorarne la progettazione. La fase di refactoring è la fase in cui pensiamo duramente al modo migliore di implementare principi e modelli di progettazione software.

È possibile modificare il codice in qualsiasi modo in cui si sceglie di migliorare la progettazione del codice. È disponibile una rete di sicurezza di unit test che impedisce l'interruzione delle funzionalità esistenti.

In questo momento, il nostro controller di gruppo è un casino dal punto di vista della buona progettazione del software. Il controller di gruppo contiene un disordine di convalida e codice di accesso ai dati. Per evitare di violare il principio di responsabilità singola, è necessario separare queste problematiche in classi diverse.

La classe controller Group refactored è contenuta nell'elenco 9. Il controller è stato modificato per usare il livello del servizio ContactManager. Si tratta dello stesso livello di servizio usato con il controller di contatto.

L'elenco 10 contiene i nuovi metodi aggiunti al livello di servizio ContactManager per supportare la convalida, l'elenco e la creazione di gruppi. L'interfaccia IContactManagerService è stata aggiornata per includere i nuovi metodi.

L'elenco 11 contiene una nuova classe FakeContactManagerRepository che implementa l'interfaccia IContactManagerRepository. A differenza della classe EntityContactManagerRepository che implementa anche l'interfaccia IContactManagerRepository, la nuova classe FakeContactManagerRepository non comunica con il database. La classe FakeContactManagerRepository usa una raccolta in memoria come proxy per il database. Questa classe verrà usata negli unit test come livello di repository fittizio.

Elenco 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

Elenco 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

Elenco 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

Per modificare l'interfaccia IContactManagerRepository è necessario usare per implementare i metodi CreateGroup() e ListGroups() nella classe EntityContactManagerRepository. Il modo più veloce e più veloce per eseguire questa operazione consiste nell'aggiungere metodi stub simili al seguente:

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

Infine, queste modifiche alla progettazione dell'applicazione richiedono di apportare alcune modifiche agli unit test. È ora necessario usare FakeContactManagerRepository durante l'esecuzione degli unit test. La classe GroupControllerTest aggiornata è contenuta nell'elenco 12.

Elenco 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

Dopo aver apportato tutte queste modifiche, anche in questo caso, tutti gli unit test vengono superati. Abbiamo completato l'intero ciclo di red/green/refactoring. Sono state implementate le prime due storie utente. Sono ora disponibili unit test di supporto per i requisiti espressi nelle storie utente. L'implementazione del resto delle storie utente comporta la ripetizione dello stesso ciclo di red/green/refactoring.

Modifica del database

Purtroppo, anche se abbiamo soddisfatto tutti i requisiti espressi dagli unit test, il nostro lavoro non viene eseguito. È comunque necessario modificare il database.

È necessario creare una nuova tabella di database group. Seguire questa procedura:

  1. Nella finestra Esplora server fare clic con il pulsante destro del mouse sulla cartella Tabelle e scegliere l'opzione di menu Aggiungi nuova tabella.
  2. Immettere le due colonne descritte di seguito nella Designer Tabella.
  3. Contrassegnare la colonna ID come chiave primaria e colonna Identity.
  4. Salvare la nuova tabella con il nome Gruppi facendo clic sull'icona del floppy.

Nome colonna Tipo di dati Consenti valori NULL
ID INT Falso
Nome nvarchar(50) Falso

Successivamente, è necessario eliminare tutti i dati dalla tabella Contatti . In caso contrario, non sarà possibile creare una relazione tra le tabelle Contatti e Gruppi. Seguire questa procedura:

  1. Fare clic con il pulsante destro del mouse sulla tabella Contatti e scegliere l'opzione di menu Mostra dati tabella.
  2. Eliminare tutte le righe.

Successivamente, è necessario definire una relazione tra la tabella di database Groups e la tabella di database Contacts esistente. Seguire questa procedura:

  1. Fare doppio clic sulla tabella Contatti nella finestra Esplora server per aprire il Designer Tabella.
  2. Aggiungere una nuova colonna integer alla tabella Contacts denominata GroupId.
  3. Fare clic sul pulsante Relazione per aprire la finestra di dialogo Relazioni chiave esterna (vedere la figura 3).
  4. Fare clic sul pulsante Aggiungi.
  5. Fare clic sul pulsante con i puntini di sospensione visualizzato accanto al pulsante Specifica tabella e colonne.
  6. Nella finestra di dialogo Tabelle e colonne selezionare Gruppi come tabella chiave primaria e ID come colonna chiave primaria. Selezionare Contatti come tabella chiave esterna e GroupId come colonna chiave esterna (vedere la figura 4). Fare clic sul pulsante OK.
  7. In INSERT e UPDATE Specification (Specifica INSERT)selezionare il valore Cascade (Cascade ) per Delete Rule (Regola di eliminazione).
  8. Fare clic sul pulsante Chiudi per chiudere la finestra di dialogo Relazioni chiave esterna.
  9. Fare clic sul pulsante Salva per salvare le modifiche apportate alla tabella Contatti.

Creazione di una relazione tra tabelle di database

Figura 03: Creazione di una relazione di tabella di database (fare clic per visualizzare l'immagine a dimensione intera)

Specifica delle relazioni tra tabelle

Figura 04: Specifica delle relazioni tra tabelle (fare clic per visualizzare l'immagine a dimensione intera)

Aggiornamento del modello di dati

Successivamente, è necessario aggiornare il modello di dati per rappresentare la nuova tabella di database. Seguire questa procedura:

  1. Fare doppio clic sul file ContactManagerModel.edmx nella cartella Models per aprire entity Designer.
  2. Fare clic con il pulsante destro del mouse sull'area di Designer e scegliere l'opzione di menu Aggiorna modello da Database.
  3. Nell'Aggiornamento guidato selezionare la tabella Gruppi e fare clic sul pulsante Fine (vedere la figura 5).
  4. Fare clic con il pulsante destro del mouse sull'entità Gruppi e scegliere l'opzione di menu Rinomina. Modificare il nome dell'entità Groups in Group (singolare).
  5. Fare clic con il pulsante destro del mouse sulla proprietà di navigazione Gruppi visualizzata nella parte inferiore dell'entità Contact. Modificare il nome della proprietà di navigazione Groups in Group (singolare).

Aggiornamento di un modello di Entity Framework dal database

Figura 05: Aggiornamento di un modello di Entity Framework dal database (fare clic per visualizzare l'immagine a dimensione intera)

Dopo aver completato questi passaggi, il modello di dati rappresenterà sia le tabelle Contatti che Gruppi. L'entità Designer deve visualizzare entrambe le entità (vedere la figura 6).

Entity Designer che visualizza gruppo e contatto

Figura 06: Entity Designer visualizzazione di Group and Contact (Fare clic per visualizzare l'immagine a dimensione intera)

Creazione delle classi di repository

Successivamente, è necessario implementare la classe del repository. Nel corso di questa iterazione sono stati aggiunti diversi nuovi metodi all'interfaccia IContactManagerRepository durante la scrittura del codice per soddisfare gli unit test. La versione finale dell'interfaccia IContactManagerRepository è contenuta nell'elenco 14.

Elenco 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

Non sono stati effettivamente implementati metodi correlati all'uso dei gruppi di contatti nella classe EntityContactManagerRepository reale. Attualmente, la classe EntityContactManagerRepository dispone di metodi stub per ognuno dei metodi del gruppo di contatti elencati nell'interfaccia IContactManagerRepository. Ad esempio, il metodo ListGroups() è simile al seguente:

Public Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerRepository.ListGroups

    throw New NotImplementedException()

End Function

I metodi stub consentono di compilare l'applicazione e di superare gli unit test. Tuttavia, ora è il momento di implementare effettivamente questi metodi. La versione finale della classe EntityContactManagerRepository è contenuta nell'elenco 13.

Elenco 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

Creazione delle visualizzazioni

ASP.NET'applicazione MVC quando si usa il motore di visualizzazione ASP.NET predefinito. Non si creano quindi visualizzazioni in risposta a uno unit test specifico. Tuttavia, poiché un'applicazione sarebbe inutile senza visualizzazioni, non è possibile completare questa iterazione senza creare e modificare le visualizzazioni contenute nell'applicazione Contact Manager.

È necessario creare le nuove visualizzazioni seguenti per la gestione dei gruppi di contatti (vedere la figura 7):

  • Views\Group\Index.aspx - Visualizza l'elenco dei gruppi di contatti
  • Views\Group\Delete.aspx - Visualizza il modulo di conferma per l'eliminazione di un gruppo di contatti

Visualizzazione Indice gruppo

Figura 07: Visualizzazione Indice gruppo (fare clic per visualizzare l'immagine a dimensione intera)

È necessario modificare le viste esistenti seguenti in modo che includano gruppi di contatti:

  • Views\Home\Create.aspx
  • Views\Home\Edit.aspx
  • Views\Home\Index.aspx

È possibile visualizzare le visualizzazioni modificate esaminando l'applicazione Visual Studio che accompagna questa esercitazione. Ad esempio, la figura 8 illustra la visualizzazione Indice contatto.

Visualizzazione Indice contatto

Figura 08: Visualizzazione Indice contatto (fare clic per visualizzare l'immagine a dimensione intera)

Riepilogo

In questa iterazione sono state aggiunte nuove funzionalità all'applicazione Contact Manager seguendo una metodologia di progettazione delle applicazioni di sviluppo basata su test. Abbiamo iniziato creando un set di storie utente. È stato creato un set di unit test che corrisponde ai requisiti espressi dalle storie utente. Infine, è stato scritto codice sufficiente per soddisfare i requisiti espressi dagli unit test.

Al termine della scrittura di codice sufficiente per soddisfare i requisiti espressi dagli unit test, è stato aggiornato il database e le viste. È stata aggiunta una nuova tabella Groups al database e è stato aggiornato il modello di dati di Entity Framework. È stato anche creato e modificato un set di visualizzazioni.

Nell'iterazione successiva, ovvero l'iterazione finale, si riscrive l'applicazione per sfruttare i vantaggi di Ajax. Sfruttando Ajax, si migliorerà la velocità di risposta e le prestazioni dell'applicazione Contact Manager.