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


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

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

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

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

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

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

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

  • Итерация 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. Когда пользователь щелкает группу контактов, отображается список соответствующих контактов.

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

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

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

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

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

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

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

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

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

Листинг 1. Controllers\GroupControllerTest.cs

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Tests.Controllers
{
    [TestClass]
    public class GroupControllerTest
    {

        [TestMethod]
        public void Index()
        {
            // Arrange
            var controller = new GroupController();

            // Act
            var result = (ViewResult)controller.Index();
        
            // Assert
            Assert.IsInstanceOfType(result.ViewData.Model, typeof(IEnumerable));
        }
    }
}

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

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

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

Листинг 2. Controllers\GroupController.cs

using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Controllers
{
    public class GroupController : Controller
    {
        public ActionResult Index()
        {
            var groups = new List();
            return View(groups);
        }

    }
}

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

namespace ContactManager.Models
{
    public class Group
    {
    }
}

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

Успех!

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

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

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

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

Листинг 4. Controllers\GroupControllerTest.cs

[TestMethod]
public void Create()
{
    // Arrange
    var controller = new GroupController();

    // Act
    var groupToCreate = new Group();
    controller.Create(groupToCreate);

    // Assert
    var result = (ViewResult)controller.Index();
    var groups = (IEnumerable<Group>)result.ViewData.Model;
    CollectionAssert.Contains(groups.ToList(), groupToCreate);
}

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

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

Листинг 5. Controllers\GroupController.cs

using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Models;
using System.Collections;

namespace ContactManager.Controllers
{
    public class GroupController : Controller
    {
        private IList<Group> _groups = new List<Group>();

        public ActionResult Index()
        {
            return View(_groups);
        }

        public ActionResult Create(Group groupToCreate)
        {
            _groups.Add(groupToCreate);
            return RedirectToAction("Index");
        }
    }
}

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

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

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

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

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

Листинг 6. Controllers\GroupControllerTest.cs

[TestMethod]
public void CreateRequiredName()
{
    // Arrange
    var controller = new GroupController();

    // Act
    var groupToCreate = new Group();
    groupToCreate.Name = String.Empty;
    var result = (ViewResult)controller.Create(groupToCreate);

    // Assert
    var error = result.ViewData.ModelState["Name"].Errors[0];
    Assert.AreEqual("Name is required.", error.ErrorMessage);
}

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

Листинг 7. Models\Group.cs

namespace ContactManager.Models
{
    public class Group
    {
        public string Name { get; set; }
    }
}

Листинг 8. Controllers\GroupController.cs

public ActionResult Create(Group groupToCreate)
{
    // Validation logic
    if (groupToCreate.Name.Trim().Length == 0)
    {
        ModelState.AddModelError("Name", "Name is required.");
        return View("Create");
    }
    
    // Database logic
    _groups.Add(groupToCreate);
    return RedirectToAction("Index");
}

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

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

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

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

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

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

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

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

Листинг 9. Controllers\GroupController.cs

using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Controllers
{
    public class GroupController : Controller
    {

        private IContactManagerService _service;

        public GroupController()
        {
            _service = new ContactManagerService(new ModelStateWrapper(this.ModelState));
        }

        public GroupController(IContactManagerService service)
        {
            _service = service;
        }

        public ActionResult Index()
        {
            return View(_service.ListGroups());
        }

        public ActionResult Create(Group groupToCreate)
        {
            if (_service.CreateGroup(groupToCreate))
                return RedirectToAction("Index");
            return View("Create");
        }
    }
}

Листинг 10. Controllers\ContactManagerService.cs

public bool ValidateGroup(Group groupToValidate)
{
    if (groupToValidate.Name.Trim().Length == 0)
       _validationDictionary.AddError("Name", "Name is required.");
    return _validationDictionary.IsValid;
}

public bool CreateGroup(Group groupToCreate)
{
    // Validation logic
    if (!ValidateGroup(groupToCreate))
        return false;

    // Database logic
    try
    {
        _repository.CreateGroup(groupToCreate);
    }
    catch
    {
        return false;
    }
    return true;
}

public IEnumerable<Group> ListGroups()
{
    return _repository.ListGroups();
}

Листинг 11. Controllers\FakeContactManagerRepository.cs

using System;
using System.Collections.Generic;
using ContactManager.Models;

namespace ContactManager.Tests.Models
{
    public class FakeContactManagerRepository : IContactManagerRepository
    {
        private IList<Group> _groups = new List<Group>(); 
        
        #region IContactManagerRepository Members

        // Group methods

        public Group CreateGroup(Group groupToCreate)
        {
            _groups.Add(groupToCreate);
            return groupToCreate;
        }

        public IEnumerable<Group> ListGroups()
        {
            return _groups;
        }

        // Contact methods
        
        public Contact CreateContact(Contact contactToCreate)
        {
            throw new NotImplementedException();
        }

        public void DeleteContact(Contact contactToDelete)
        {
            throw new NotImplementedException();
        }

        public Contact EditContact(Contact contactToEdit)
        {
            throw new NotImplementedException();
        }

        public Contact GetContact(int id)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<Contact> ListContacts()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

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

public Group CreateGroup(Group groupToCreate)
{
    throw new NotImplementedException();
}

public IEnumerable<Group> ListGroups()
{
    throw new NotImplementedException();
}

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

Листинг 12. Controllers\GroupControllerTest.cs

using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Controllers;
using ContactManager.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections;
using System.Linq;
using System;
using ContactManager.Tests.Models;

namespace ContactManager.Tests.Controllers
{
    [TestClass]
    public class GroupControllerTest
    {
        private IContactManagerRepository _repository;
        private ModelStateDictionary _modelState;
        private IContactManagerService _service;

        [TestInitialize]
        public void Initialize()
        {
            _repository = new FakeContactManagerRepository();
            _modelState = new ModelStateDictionary();
            _service = new ContactManagerService(new ModelStateWrapper(_modelState), _repository);

        }

        [TestMethod]
        public void Index()
        {
            // Arrange
            var controller = new GroupController(_service);

            // Act
            var result = (ViewResult)controller.Index();
        
            // Assert
            Assert.IsInstanceOfType(result.ViewData.Model, typeof(IEnumerable));
        }

        [TestMethod]
        public void Create()
        {
            // Arrange
            var controller = new GroupController(_service);

            // Act
            var groupToCreate = new Group();
            groupToCreate.Name = "Business";
            controller.Create(groupToCreate);

            // Assert
            var result = (ViewResult)controller.Index();
            var groups = (IEnumerable)result.ViewData.Model;
            CollectionAssert.Contains(groups.ToList(), groupToCreate);
        }

        [TestMethod]
        public void CreateRequiredName()
        {
            // Arrange
            var controller = new GroupController(_service);

            // Act
            var groupToCreate = new Group();
            groupToCreate.Name = String.Empty;
            var result = (ViewResult)controller.Create(groupToCreate);

            // Assert
            var error = _modelState["Name"].Errors[0];
            Assert.AreEqual("Name is required.", error.ErrorMessage);
        }
    
    }
}

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

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

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

Необходимо создать таблицу базы данных 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.cs

using System.Collections.Generic;

namespace ContactManager.Models
{
    public interface IContactManagerRepository
    {
        // Contact methods
        Contact CreateContact(int groupId, Contact contactToCreate);
        void DeleteContact(Contact contactToDelete);
        Contact EditContact(int groupId, Contact contactToEdit);
        Contact GetContact(int id);

        // Group methods
        Group CreateGroup(Group groupToCreate);
        IEnumerable<Group> ListGroups();
        Group GetGroup(int groupId);
        Group GetFirstGroup();
        void DeleteGroup(Group groupToDelete);
    }
}

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

public IEnumerable<Group> ListGroups()
{
    throw new NotImplementedException();
}

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

Листинг 13. Models\EntityContactManagerRepository.cs

using System.Collections.Generic;
using System.Linq;
using System;

namespace ContactManager.Models
{
    public class EntityContactManagerRepository : ContactManager.Models.IContactManagerRepository
    {
        private ContactManagerDBEntities _entities = new ContactManagerDBEntities();

        // Contact methods

        public Contact GetContact(int id)
        {
            return (from c in _entities.ContactSet.Include("Group")
                    where c.Id == id
                    select c).FirstOrDefault();
        }

        public Contact CreateContact(int groupId, Contact contactToCreate)
        {
            // Associate group with contact
            contactToCreate.Group = GetGroup(groupId);

            // Save new contact
            _entities.AddToContactSet(contactToCreate);
            _entities.SaveChanges();
            return contactToCreate;
        }

        public Contact EditContact(int groupId, Contact contactToEdit)
        {
            // Get original contact
            var originalContact = GetContact(contactToEdit.Id);
            
            // Update with new group
            originalContact.Group = GetGroup(groupId);
            
            // Save changes
            _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit);
            _entities.SaveChanges();
            return contactToEdit;
        }

        public void DeleteContact(Contact contactToDelete)
        {
            var originalContact = GetContact(contactToDelete.Id);
            _entities.DeleteObject(originalContact);
            _entities.SaveChanges();
        }

        public Group CreateGroup(Group groupToCreate)
        {
            _entities.AddToGroupSet(groupToCreate);
            _entities.SaveChanges();
            return groupToCreate;
        }

        // Group Methods

        public IEnumerable<Group> ListGroups()
        {
            return _entities.GroupSet.ToList();
        }

        public Group GetFirstGroup()
        {
            return _entities.GroupSet.Include("Contacts").FirstOrDefault();
        }

        public Group GetGroup(int id)
        {
            return (from g in _entities.GroupSet.Include("Contacts")
                       where g.Id == id
                       select g).FirstOrDefault();
        }

        public void DeleteGroup(Group groupToDelete)
        {
            var originalGroup = GetGroup(groupToDelete.Id);
            _entities.DeleteObject(originalGroup);
            _entities.SaveChanges();

        }

    }
}

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

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, мы улучшаем скорость реагирования и производительность приложения Диспетчера контактов.