Поделиться через


Итерация 6. Использование разработки, управляемой тестами (VB)

от Майкрософт

Скачивание кода

В этой шестой итерации мы добавим новые функции в приложение, сначала написав модульные тесты и написав код для модульных тестов. В этой итерации мы добавим группы контактов.

Создание приложения управления контактами ASP.NET MVC (VB)

В этой серии учебников мы создадим все приложение управления контактами от начала до конца. Приложение Диспетчер контактов позволяет хранить контактные данные ( имена, номера телефонов и адреса электронной почты) для списка людей.

Мы создаем приложение с помощью нескольких итераций. С каждой итерацией мы постепенно улучшаем приложение. Цель этого подхода с несколькими итерациями — дать возможность понять причину каждого изменения.

  • Итерация 1. Создание приложения. В первой итерации мы создадим диспетчер контактов самым простым способом. Добавлена поддержка базовых операций с базами данных: создание, чтение, обновление и удаление (CRUD).

  • Итерация 2. Сделайте приложение красивым. В этой итерации мы улучшаем внешний вид приложения, изменив ASP.NET по умолчанию представление MVC master страницу и каскадную таблицу стилей.

  • Итерация 3. Добавление проверки формы. В третьей итерации мы добавим базовую проверку формы. Мы запрещаем пользователям отправлять форму без заполнения обязательных полей формы. Мы также проверяем адреса электронной почты и номера телефонов.

  • Итерация 4. Сделайте приложение слабосвязанным. В этой четвертой итерации мы воспользуемся преимуществами нескольких шаблонов проектирования программного обеспечения, чтобы упростить обслуживание и изменение приложения Диспетчера контактов. Например, мы рефакторинг приложения для использования шаблона репозитория и шаблона внедрения зависимостей.

  • Итерация 5. Создание модульных тестов. В пятой итерации мы упростим обслуживание и изменение приложения, добавив модульные тесты. Мы имитируем классы модели данных и создаем модульные тесты для наших контроллеров и логики проверки.

  • Итерация 6. Использование разработки на основе тестирования. В этой шестой итерации мы добавим новые функции в приложение, сначала написав модульные тесты и написав код для модульных тестов. В этой итерации мы добавим группы контактов.

  • Итерация 7. Добавление функциональных возможностей Ajax. В седьмой итерации мы повышаем скорость реагирования и производительность приложения, добавляя поддержку Ajax.

Эта итерация

В предыдущей итерации приложения Диспетчера контактов мы создали модульные тесты, чтобы обеспечить безопасность для нашего кода. Цель создания модульных тестов заключалась в том, чтобы сделать код более устойчивым к изменениям. С помощью модульных тестов мы с радостью можем внести любые изменения в код и сразу узнать, не нарушили ли существующие функциональные возможности.

В этой итерации мы используем модульные тесты для совершенно другой цели. В этой итерации мы используем модульные тесты как часть философии проектирования приложений, которая называется разработка на основе тестов. Когда вы практикуете разработку на основе тестов, вы сначала пишете тесты, а затем пишете код для тестов.

Точнее, при практической разработке на основе тестирования при создании кода необходимо выполнить три шага (красный, зеленый или рефакторинг):

  1. Написание модульного теста, который завершается сбоем (красный цвет)
  2. Написание кода, который проходит модульный тест (зеленый)
  3. Рефакторинг кода (рефакторинг)

Сначала необходимо написать модульный тест. Модульный тест должен выразить ваше намерение о том, как вы ожидаете поведение кода. При первом создании модульного теста модульный тест должен завершиться ошибкой. Тест должен завершиться ошибкой, так как вы еще не написали код приложения, удовлетворяющий тесту.

Далее необходимо написать достаточно кода, чтобы пройти модульный тест. Цель состоит в том, чтобы написать код самым ленивейшим, небрежным и быстрым способом. Не стоит тратить время на размышления об архитектуре приложения. Вместо этого следует сосредоточиться на написании минимального объема кода, необходимого для удовлетворения намерения, выраженного модульным тестом.

Наконец, после написания достаточного количества кода можно сделать шаг назад и рассмотреть общую архитектуру приложения. На этом шаге вы перепишите (рефакторинг) код, используя преимущества шаблонов разработки программного обеспечения, таких как шаблон репозитория, чтобы код был более пригодным для обслуживания. Вы можете без страха переписать код на этом шаге, так как код охватывается модульными тестами.

Есть много преимуществ, которые дает практика разработки на основе тестов. Во-первых, разработка на основе тестов заставляет вас сосредоточиться на коде, который фактически необходимо написать. Так как вы постоянно сосредоточены на написании достаточного объема кода для прохождения определенного теста, вы не сможете бродить по сорнякам и писать огромные объемы кода, которые никогда не будут использоваться.

Во-вторых, методология проектирования "сначала протестировать" заставляет писать код с точки зрения того, как будет использоваться ваш код. Другими словами, при практической разработке на основе тестов вы постоянно пишете тесты с точки зрения пользователя. Таким образом, разработка на основе тестирования может привести к более понятным и понятным API.

Наконец, разработка на основе тестирования заставляет писать модульные тесты в рамках обычного процесса написания приложения. По мере приближения крайнего срока проекта тестирование обычно является первым, что выходит из окна. С другой стороны, при разработке на основе тестов вы, скорее всего, будете уметь писать модульные тесты, так как разработка на основе тестов делает модульные тесты центральным элементом процесса создания приложения.

Примечание

Чтобы узнать больше о разработке на основе тестов, рекомендуется ознакомиться с книгой Майкла Перья эффективная работа с устаревшим кодом.

В этой итерации мы добавим новую функцию в приложение Диспетчера контактов. Добавлена поддержка групп контактов. Группы контактов можно использовать для организации контактов по таким категориям, как бизнес-группы и группы друзей.

Мы добавим эту новую функцию в наше приложение, выполнив процесс разработки на основе тестирования. Сначала мы напишем модульные тесты и напишем весь код для этих тестов.

Что тестируется

Как мы уже говорили в предыдущей итерации, модульные тесты для логики доступа к данным или логики представления обычно не записываются. Модульные тесты для логики доступа к данным не записываются, так как доступ к базе данных является относительно медленной операцией. Модульные тесты для логики представления не записываются, так как для доступа к представлению требуется запустить веб-сервер, что является относительно медленной операцией. Не следует писать модульный тест, если тест не может выполняться снова и снова очень быстро.

Так как разработка на основе тестирования управляется модульными тестами, сначала мы сосредоточимся на написании контроллера и бизнес-логики. Мы избегаем касания базы данных или представлений. Мы не будем изменять базу данных или создавать представления до самого конца этого руководства. Начнем с того, что можно протестировать.

Создание пользовательских историй

При практической разработке на основе тестирования вы всегда начинаете с написания теста. Сразу же возникает вопрос: Как вы решите, какой тест написать в первую очередь? Чтобы ответить на этот вопрос, необходимо написать набор пользовательских историй.

История пользователя — это очень краткое (обычно одно предложение) описание требования к программному обеспечению. Это должно быть нетехнических описание требования, написанное с точки зрения пользователя.

Ниже приведен набор пользовательских историй, описывающих функции, необходимые для новой функции группы контактов:

  1. Пользователь может просматривать список групп контактов.
  2. Пользователь может создать новую группу контактов.
  3. Пользователь может удалить существующую группу контактов.
  4. Пользователь может выбрать группу контактов при создании нового контакта.
  5. Пользователь может выбрать группу контактов при редактировании существующего контакта.
  6. Список групп контактов отображается в представлении Индекс.
  7. Когда пользователь щелкает группу контактов, отображается список соответствующих контактов.

Обратите внимание, что этот список пользовательских историй полностью понятен клиенту. Нет упоминание сведений о технической реализации.

В процессе создания приложения набор пользовательских историй может стать более утонченным. Вы можете разбить историю пользователя на несколько историй (требований). Например, вы можете решить, что создание новой группы контактов должно включать проверку. Отправка группы контактов без имени должна возвращать ошибку проверки.

После создания списка пользовательских историй можно приступать к написанию первого модульного теста. Начнем с создания модульного теста для просмотра списка групп контактов.

Перечисление групп контактов

Наша первая история пользователя заключается в том, что пользователь должен иметь возможность просматривать список групп контактов. Мы должны выразить эту историю с помощью теста.

Создайте модульный тест, щелкнув правой кнопкой мыши папку Controllers в проекте ContactManager.Tests, выбрав Добавить, Создать тест и выбрав шаблон Модульный тест (см. рис. 1). Назовите новый модульный тест GroupControllerTest.vb и нажмите кнопку ОК .

Добавление модульного теста GroupControllerTest

Рис. 01. Добавление модульного теста GroupControllerTest (Щелкните для просмотра полноразмерного изображения)

Наш первый модульный тест приведен в листинге 1. Этот тест проверяет, возвращает ли метод Index() контроллера Group набор групп. Тест проверяет, возвращается ли коллекция Групп в данных просмотра.

Листинг 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

При первом вводе кода из листинга 1 в Visual Studio вы получите много красных волнистых линий. Классы GroupController или Group не созданы.

На этом этапе мы даже не можем создать приложение, чтобы мы не могли выполнить наш первый модульный тест. Это хорошо. Это считается неудачным тестом. Таким образом, теперь у нас есть разрешение на написание кода приложения. Нам нужно написать достаточно кода для выполнения теста.

Класс контроллера Group в листинге 2 содержит минимальный объем кода, необходимый для прохождения модульного теста. Действие Index() возвращает статически закодированный список Групп (класс Group определен в листинге 3).

Листинг 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

Листинг 3. Models\Group.vb

Public Class Group

End Class

После добавления классов GroupController и Group в наш проект первый модульный тест завершается успешно (см. рис. 2). Мы выполнили минимальную работу, необходимую для прохождения теста. Пора праздновать.

Успех!

Рис. 02. Успех! (Щелкните для просмотра полноразмерного изображения)

Создание групп контактов

Теперь можно перейти ко второй пользовательской истории. Мы должны иметь возможность создавать новые группы контактов. Мы должны выразить это намерение с помощью теста.

Тест в листинге 4 проверяет, что вызов метода Create() с помощью новой группы добавляет группу в список Групп, возвращаемых методом Index(). Иными словами, если я создаю новую группу, я смогу вернуть новую группу из списка Групп, возвращаемых методом Index().

Листинг 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

Тест в листинге 4 вызывает метод Create() контроллера группы с новой группой контактов. Затем тест проверяет, что вызов метода Index() контроллера группы возвращает новую группу в представлении данных.

Измененный контроллер группы в листинге 5 содержит минимум изменений, необходимых для прохождения нового теста.

Листинг 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

Контроллер группы в листинге 5 имеет новое действие Create(). Это действие добавляет группу в коллекцию Групп. Обратите внимание, что действие Index() было изменено для возврата содержимого коллекции Групп.

Опять же, мы выполнили минимальный объем работы, необходимый для прохождения модульного теста. После внесения этих изменений в контроллер группы все модульные тесты будут пройдены.

Добавление проверки

Это требование не было явно указано в пользовательской истории. Однако разумно требовать, чтобы у группы было имя. В противном случае организация контактов в группы была бы не очень полезной.

В листинге 6 содержится новый тест, который выражает это намерение. Этот тест проверяет, что попытка создать группу без указания имени приводит к выводу сообщения об ошибке проверки в состоянии модели.

Листинг 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

Чтобы выполнить этот тест, необходимо добавить свойство Name в класс Group (см. листинг 7). Кроме того, нам нужно добавить небольшой фрагмент логики проверки в действие Create() контроллера группы (см. листинг 8).

Листинг 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

Листинг 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

Обратите внимание, что действие Create() контроллера группы теперь содержит как проверку, так и логику базы данных. В настоящее время база данных, используемая контроллером группы, состоит не более чем из коллекции в памяти.

Время рефакторинга

Третий шаг в красном, зеленом или рефакторинге — рефакторинг. На этом этапе необходимо отойти от нашего кода и подумать о том, как можно рефакторинговать приложение, чтобы улучшить его структуру. Этап рефакторинг — это этап, на котором мы думаем о том, как лучше всего реализовать принципы и шаблоны разработки программного обеспечения.

Мы можем изменять код любым способом, чтобы улучшить его структуру. У нас есть подстраховка модульных тестов, которые не позволяют нам нарушить существующую функциональность.

Сейчас наш контроллер группы представляет собой беспорядок с точки зрения хорошего проектирования программного обеспечения. Контроллер группы содержит запутанный беспорядок кода проверки и доступа к данным. Чтобы избежать нарушения принципа единой ответственности, необходимо разделить эти проблемы на разные классы.

Класс контроллера группы с рефакторингом приведен в листинге 9. Контроллер был изменен для использования уровня служб ContactManager. Это тот же уровень служб, который мы используем с контроллером contact.

В листинге 10 содержатся новые методы, добавленные на уровень служб ContactManager для поддержки проверки, перечисления и создания групп. Интерфейс IContactManagerService был обновлен для включения новых методов.

В листинге 11 содержится новый класс FakeContactManagerRepository, реализующий интерфейс IContactManagerRepository. В отличие от класса EntityContactManagerRepository, который также реализует интерфейс IContactManagerRepository, наш новый класс FakeContactManagerRepository не взаимодействует с базой данных. Класс FakeContactManagerRepository использует коллекцию в памяти в качестве прокси-сервера для базы данных. Мы будем использовать этот класс в модульных тестах в качестве поддельного уровня репозитория.

Листинг 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

Листинг 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

Листинг 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

Изменение интерфейса IContactManagerRepository требует использования для реализации методов CreateGroup() и ListGroups() в классе EntityContactManagerRepository. Самый ленивый и быстрый способ сделать это — добавить методы-заглушки, которые выглядят следующим образом:

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

Наконец, эти изменения в структуре приложения требуют внесения некоторых изменений в модульные тесты. Теперь при выполнении модульных тестов необходимо использовать FakeContactManagerRepository. Обновленный класс GroupControllerTest содержится в листинге 12.

Листинг 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

После внесения всех этих изменений все модульные тесты снова будут пройдены. Мы завершили весь цикл красного, зеленого и рефакторинга. Мы реализовали первые две пользовательские истории. Теперь у нас есть вспомогательные модульные тесты для требований, выраженных в пользовательских историях. Реализация оставшихся пользовательских историй включает повторение одного и того же цикла красного, зеленого и рефакторинга.

Изменение базы данных

К сожалению, несмотря на то, что мы выполнили все требования, выраженные в модульных тестах, наша работа не выполнена. Нам по-прежнему нужно изменить базу данных.

Нам нужно создать таблицу базы данных group. Выполните следующие действия.

  1. В окне Сервер Обозреватель щелкните правой кнопкой мыши папку Таблицы и выберите пункт меню Добавить новую таблицу.
  2. Введите два столбца, описанные ниже в Designer таблицы.
  3. Пометьте столбец Id как первичный ключ и Столбец Идентификатор.
  4. Сохраните новую таблицу с именем Группы, щелкнув значок дискеты.

Имя столбца Тип данных Разрешить значения NULL
Идентификатор INT Неверно
Имя nvarchar(50) Неверно

Затем необходимо удалить все данные из таблицы "Контакты" (в противном случае мы не сможем создать связь между таблицами "Контакты" и "Группы"). Выполните следующие действия.

  1. Щелкните правой кнопкой мыши таблицу Контакты и выберите пункт меню Показать данные таблицы.
  2. Удалите все строки.

Далее необходимо определить связь между таблицей базы данных Groups и существующей таблицей базы данных Contacts. Выполните следующие действия.

  1. Дважды щелкните таблицу Контакты в окне Серверная Обозреватель, чтобы открыть Designer таблицы.
  2. Добавьте в таблицу Контактов новый целочисленный столбец с именем GroupId.
  3. Нажмите кнопку Связь, чтобы открыть диалоговое окно Связи с внешним ключом (см. рис. 3).
  4. Нажмите кнопку Добавить.
  5. Нажмите кнопку с многоточием, которая отображается рядом с кнопкой Спецификация таблиц и столбцов.
  6. В диалоговом окне Таблицы и столбцы выберите Группы в качестве таблицы первичного ключа и Id в качестве первичного ключевого столбца. Выберите Контакты в качестве таблицы внешних ключей и GroupId в качестве столбца внешнего ключа (см. рис. 4). Нажмите кнопку "ОК".
  7. В разделе Insert and UPDATE Specification (Спецификация INSERT и UPDATE) выберите значение Cascade (Каскад) для параметра Delete Rule (Удаление правила).
  8. Нажмите кнопку Закрыть, чтобы закрыть диалоговое окно Связи с внешним ключом.
  9. Нажмите кнопку Сохранить, чтобы сохранить изменения в таблице Контакты.

Создание связи между таблицами базы данных

Рис. 03. Создание связи между таблицами базы данных (щелкните для просмотра полноразмерного изображения)

Указание связей между таблицами

Рис. 04. Указание связей между таблицами (щелкните для просмотра полноразмерного изображения)

Обновление модели данных

Далее необходимо обновить модель данных, чтобы она представляла новую таблицу базы данных. Выполните следующие действия.

  1. Дважды щелкните файл ContactManagerModel.edmx в папке Models, чтобы открыть Designer сущностей.
  2. Щелкните правой кнопкой мыши область Designer и выберите пункт меню Обновить модель из базы данных.
  3. В мастере обновления выберите таблицу Группы и нажмите кнопку Готово (см. рис. 5).
  4. Щелкните правой кнопкой мыши сущность Groups и выберите пункт меню Переименовать. Измените имя сущности Groups на Group (в единственном числе).
  5. Щелкните правой кнопкой мыши свойство навигации Группы, которое отображается в нижней части сущности Contact. Измените имя свойства навигации Группы на Группа (в единственном числе).

Обновление модели Entity Framework из базы данных

Рис. 05. Обновление модели Entity Framework из базы данных (щелкните для просмотра полноразмерного изображения)

После выполнения этих действий модель данных будет представлять таблицы Контакты и Группы. В Designer сущности должны отображаться обе сущности (см. рис. 6).

Designer сущностей с отображением группы и контакта

Рис. 06. Designer сущностей, отображающих группу и контакт(щелкните для просмотра полноразмерного изображения)

Создание классов репозитория

Далее необходимо реализовать класс репозитория. В ходе этой итерации мы добавили несколько новых методов в интерфейс IContactManagerRepository при написании кода для удовлетворения наших модульных тестов. Окончательная версия интерфейса IContactManagerRepository приведена в листинге 14.

Листинг 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

Мы не реализовали ни один из методов, связанных с работой с группами контактов в нашем реальном классе EntityContactManagerRepository. В настоящее время класс EntityContactManagerRepository содержит методы-заглушки для каждого из методов группы контактов, перечисленных в интерфейсе IContactManagerRepository. Например, метод ListGroups() в настоящее время выглядит следующим образом:

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

    throw New NotImplementedException()

End Function

Методы-заглушки позволили скомпилировать приложение и пройти модульные тесты. Однако теперь пришло время реализовать эти методы. Окончательная версия класса EntityContactManagerRepository приведена в листинге 13.

Листинг 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

Создание представлений

ASP.NET приложение MVC при использовании обработчика представлений ASP.NET по умолчанию. Таким образом, представления не создаются в ответ на конкретный модульный тест. Однако, поскольку приложение будет бесполезным без представлений, мы не можем завершить эту итерацию без создания и изменения представлений, содержащихся в приложении Диспетчера контактов.

Нам нужно создать следующие новые представления для управления группами контактов (см. рис. 7):

  • Views\Group\Index.aspx — отображает список групп контактов
  • Views\Group\Delete.aspx — отображает форму подтверждения удаления группы контактов.

Представление

Рис. 07. Представление "Индекс группы" (щелкните для просмотра полноразмерного изображения)

Необходимо изменить следующие существующие представления, чтобы они включали группы контактов:

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

Вы можете просмотреть измененные представления, просмотрев приложение Visual Studio, сопровождающее это руководство. Например, на рисунке 8 показано представление "Индекс контактов".

Представление

Рис. 08. Представление "Индекс контактов" (щелкните для просмотра полноразмерного изображения)

Итоги

В этой итерации мы добавили новые функциональные возможности в приложение Диспетчера контактов, следуя методологии проектирования приложений разработки на основе тестирования. Мы начали с создания набора пользовательских историй. Мы создали набор модульных тестов, которые соответствуют требованиям, выраженным в пользовательских историях. Наконец, мы написали достаточно кода для удовлетворения требований, выраженных модульными тестами.

Завершив написание достаточного объема кода для удовлетворения требований, выраженных модульными тестами, мы обновили базу данных и представления. Мы добавили новую таблицу Groups в базу данных и обновили модель данных Entity Framework. Мы также создали и изменили набор представлений.

В следующей итерации - окончательной итерации - мы переписываем наше приложение, чтобы воспользоваться преимуществами Ajax. Используя Ajax, мы улучшаем скорость реагирования и производительность приложения Диспетчера контактов.