Итерация 6. Использование разработки, управляемой тестами (VB)
от Майкрософт
В этой шестой итерации мы добавим новые функции в приложение, сначала написав модульные тесты и написав код для модульных тестов. В этой итерации мы добавим группы контактов.
Создание приложения управления контактами ASP.NET MVC (VB)
В этой серии учебников мы создадим все приложение управления контактами от начала до конца. Приложение Диспетчер контактов позволяет хранить контактные данные ( имена, номера телефонов и адреса электронной почты) для списка людей.
Мы создаем приложение с помощью нескольких итераций. С каждой итерацией мы постепенно улучшаем приложение. Цель этого подхода с несколькими итерациями — дать возможность понять причину каждого изменения.
Итерация 1. Создание приложения. В первой итерации мы создадим диспетчер контактов самым простым способом. Добавлена поддержка базовых операций с базами данных: создание, чтение, обновление и удаление (CRUD).
Итерация 2. Сделайте приложение красивым. В этой итерации мы улучшаем внешний вид приложения, изменив ASP.NET по умолчанию представление MVC master страницу и каскадную таблицу стилей.
Итерация 3. Добавление проверки формы. В третьей итерации мы добавим базовую проверку формы. Мы запрещаем пользователям отправлять форму без заполнения обязательных полей формы. Мы также проверяем адреса электронной почты и номера телефонов.
Итерация 4. Сделайте приложение слабосвязанным. В этой четвертой итерации мы воспользуемся преимуществами нескольких шаблонов проектирования программного обеспечения, чтобы упростить обслуживание и изменение приложения Диспетчера контактов. Например, мы рефакторинг приложения для использования шаблона репозитория и шаблона внедрения зависимостей.
Итерация 5. Создание модульных тестов. В пятой итерации мы упростим обслуживание и изменение приложения, добавив модульные тесты. Мы имитируем классы модели данных и создаем модульные тесты для наших контроллеров и логики проверки.
Итерация 6. Использование разработки на основе тестирования. В этой шестой итерации мы добавим новые функции в приложение, сначала написав модульные тесты и написав код для модульных тестов. В этой итерации мы добавим группы контактов.
Итерация 7. Добавление функциональных возможностей Ajax. В седьмой итерации мы повышаем скорость реагирования и производительность приложения, добавляя поддержку Ajax.
Эта итерация
В предыдущей итерации приложения Диспетчера контактов мы создали модульные тесты, чтобы обеспечить безопасность для нашего кода. Цель создания модульных тестов заключалась в том, чтобы сделать код более устойчивым к изменениям. С помощью модульных тестов мы с радостью можем внести любые изменения в код и сразу узнать, не нарушили ли существующие функциональные возможности.
В этой итерации мы используем модульные тесты для совершенно другой цели. В этой итерации мы используем модульные тесты как часть философии проектирования приложений, которая называется разработка на основе тестов. Когда вы практикуете разработку на основе тестов, вы сначала пишете тесты, а затем пишете код для тестов.
Точнее, при практической разработке на основе тестирования при создании кода необходимо выполнить три шага (красный, зеленый или рефакторинг):
- Написание модульного теста, который завершается сбоем (красный цвет)
- Написание кода, который проходит модульный тест (зеленый)
- Рефакторинг кода (рефакторинг)
Сначала необходимо написать модульный тест. Модульный тест должен выразить ваше намерение о том, как вы ожидаете поведение кода. При первом создании модульного теста модульный тест должен завершиться ошибкой. Тест должен завершиться ошибкой, так как вы еще не написали код приложения, удовлетворяющий тесту.
Далее необходимо написать достаточно кода, чтобы пройти модульный тест. Цель состоит в том, чтобы написать код самым ленивейшим, небрежным и быстрым способом. Не стоит тратить время на размышления об архитектуре приложения. Вместо этого следует сосредоточиться на написании минимального объема кода, необходимого для удовлетворения намерения, выраженного модульным тестом.
Наконец, после написания достаточного количества кода можно сделать шаг назад и рассмотреть общую архитектуру приложения. На этом шаге вы перепишите (рефакторинг) код, используя преимущества шаблонов разработки программного обеспечения, таких как шаблон репозитория, чтобы код был более пригодным для обслуживания. Вы можете без страха переписать код на этом шаге, так как код охватывается модульными тестами.
Есть много преимуществ, которые дает практика разработки на основе тестов. Во-первых, разработка на основе тестов заставляет вас сосредоточиться на коде, который фактически необходимо написать. Так как вы постоянно сосредоточены на написании достаточного объема кода для прохождения определенного теста, вы не сможете бродить по сорнякам и писать огромные объемы кода, которые никогда не будут использоваться.
Во-вторых, методология проектирования "сначала протестировать" заставляет писать код с точки зрения того, как будет использоваться ваш код. Другими словами, при практической разработке на основе тестов вы постоянно пишете тесты с точки зрения пользователя. Таким образом, разработка на основе тестирования может привести к более понятным и понятным API.
Наконец, разработка на основе тестирования заставляет писать модульные тесты в рамках обычного процесса написания приложения. По мере приближения крайнего срока проекта тестирование обычно является первым, что выходит из окна. С другой стороны, при разработке на основе тестов вы, скорее всего, будете уметь писать модульные тесты, так как разработка на основе тестов делает модульные тесты центральным элементом процесса создания приложения.
Примечание
Чтобы узнать больше о разработке на основе тестов, рекомендуется ознакомиться с книгой Майкла Перья эффективная работа с устаревшим кодом.
В этой итерации мы добавим новую функцию в приложение Диспетчера контактов. Добавлена поддержка групп контактов. Группы контактов можно использовать для организации контактов по таким категориям, как бизнес-группы и группы друзей.
Мы добавим эту новую функцию в наше приложение, выполнив процесс разработки на основе тестирования. Сначала мы напишем модульные тесты и напишем весь код для этих тестов.
Что тестируется
Как мы уже говорили в предыдущей итерации, модульные тесты для логики доступа к данным или логики представления обычно не записываются. Модульные тесты для логики доступа к данным не записываются, так как доступ к базе данных является относительно медленной операцией. Модульные тесты для логики представления не записываются, так как для доступа к представлению требуется запустить веб-сервер, что является относительно медленной операцией. Не следует писать модульный тест, если тест не может выполняться снова и снова очень быстро.
Так как разработка на основе тестирования управляется модульными тестами, сначала мы сосредоточимся на написании контроллера и бизнес-логики. Мы избегаем касания базы данных или представлений. Мы не будем изменять базу данных или создавать представления до самого конца этого руководства. Начнем с того, что можно протестировать.
Создание пользовательских историй
При практической разработке на основе тестирования вы всегда начинаете с написания теста. Сразу же возникает вопрос: Как вы решите, какой тест написать в первую очередь? Чтобы ответить на этот вопрос, необходимо написать набор пользовательских историй.
История пользователя — это очень краткое (обычно одно предложение) описание требования к программному обеспечению. Это должно быть нетехнических описание требования, написанное с точки зрения пользователя.
Ниже приведен набор пользовательских историй, описывающих функции, необходимые для новой функции группы контактов:
- Пользователь может просматривать список групп контактов.
- Пользователь может создать новую группу контактов.
- Пользователь может удалить существующую группу контактов.
- Пользователь может выбрать группу контактов при создании нового контакта.
- Пользователь может выбрать группу контактов при редактировании существующего контакта.
- Список групп контактов отображается в представлении Индекс.
- Когда пользователь щелкает группу контактов, отображается список соответствующих контактов.
Обратите внимание, что этот список пользовательских историй полностью понятен клиенту. Нет упоминание сведений о технической реализации.
В процессе создания приложения набор пользовательских историй может стать более утонченным. Вы можете разбить историю пользователя на несколько историй (требований). Например, вы можете решить, что создание новой группы контактов должно включать проверку. Отправка группы контактов без имени должна возвращать ошибку проверки.
После создания списка пользовательских историй можно приступать к написанию первого модульного теста. Начнем с создания модульного теста для просмотра списка групп контактов.
Перечисление групп контактов
Наша первая история пользователя заключается в том, что пользователь должен иметь возможность просматривать список групп контактов. Мы должны выразить эту историю с помощью теста.
Создайте модульный тест, щелкнув правой кнопкой мыши папку Controllers в проекте ContactManager.Tests, выбрав Добавить, Создать тест и выбрав шаблон Модульный тест (см. рис. 1). Назовите новый модульный тест GroupControllerTest.vb и нажмите кнопку ОК .
Рис. 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. Выполните следующие действия.
- В окне Сервер Обозреватель щелкните правой кнопкой мыши папку Таблицы и выберите пункт меню Добавить новую таблицу.
- Введите два столбца, описанные ниже в Designer таблицы.
- Пометьте столбец Id как первичный ключ и Столбец Идентификатор.
- Сохраните новую таблицу с именем Группы, щелкнув значок дискеты.
Имя столбца | Тип данных | Разрешить значения NULL |
---|---|---|
Идентификатор | INT | Неверно |
Имя | nvarchar(50) | Неверно |
Затем необходимо удалить все данные из таблицы "Контакты" (в противном случае мы не сможем создать связь между таблицами "Контакты" и "Группы"). Выполните следующие действия.
- Щелкните правой кнопкой мыши таблицу Контакты и выберите пункт меню Показать данные таблицы.
- Удалите все строки.
Далее необходимо определить связь между таблицей базы данных Groups и существующей таблицей базы данных Contacts. Выполните следующие действия.
- Дважды щелкните таблицу Контакты в окне Серверная Обозреватель, чтобы открыть Designer таблицы.
- Добавьте в таблицу Контактов новый целочисленный столбец с именем GroupId.
- Нажмите кнопку Связь, чтобы открыть диалоговое окно Связи с внешним ключом (см. рис. 3).
- Нажмите кнопку Добавить.
- Нажмите кнопку с многоточием, которая отображается рядом с кнопкой Спецификация таблиц и столбцов.
- В диалоговом окне Таблицы и столбцы выберите Группы в качестве таблицы первичного ключа и Id в качестве первичного ключевого столбца. Выберите Контакты в качестве таблицы внешних ключей и GroupId в качестве столбца внешнего ключа (см. рис. 4). Нажмите кнопку "ОК".
- В разделе Insert and UPDATE Specification (Спецификация INSERT и UPDATE) выберите значение Cascade (Каскад) для параметра Delete Rule (Удаление правила).
- Нажмите кнопку Закрыть, чтобы закрыть диалоговое окно Связи с внешним ключом.
- Нажмите кнопку Сохранить, чтобы сохранить изменения в таблице Контакты.
Рис. 03. Создание связи между таблицами базы данных (щелкните для просмотра полноразмерного изображения)
Рис. 04. Указание связей между таблицами (щелкните для просмотра полноразмерного изображения)
Обновление модели данных
Далее необходимо обновить модель данных, чтобы она представляла новую таблицу базы данных. Выполните следующие действия.
- Дважды щелкните файл ContactManagerModel.edmx в папке Models, чтобы открыть Designer сущностей.
- Щелкните правой кнопкой мыши область Designer и выберите пункт меню Обновить модель из базы данных.
- В мастере обновления выберите таблицу Группы и нажмите кнопку Готово (см. рис. 5).
- Щелкните правой кнопкой мыши сущность Groups и выберите пункт меню Переименовать. Измените имя сущности Groups на Group (в единственном числе).
- Щелкните правой кнопкой мыши свойство навигации Группы, которое отображается в нижней части сущности Contact. Измените имя свойства навигации Группы на Группа (в единственном числе).
Рис. 05. Обновление модели Entity Framework из базы данных (щелкните для просмотра полноразмерного изображения)
После выполнения этих действий модель данных будет представлять таблицы Контакты и Группы. В Designer сущности должны отображаться обе сущности (см. рис. 6).
Рис. 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, мы улучшаем скорость реагирования и производительность приложения Диспетчера контактов.
Обратная связь
https://aka.ms/ContentUserFeedback.
Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделеОтправить и просмотреть отзыв по