Описание модульного тестирования

81

В статье «Простое приложение ASP.NET Web Forms 4.5» был продемонстрирован процесс построения простого приложения ASP.NET Web Forms, предназначенного для обработки приглашений на новогоднюю вечеринку. Процесс был несложным и быстрым, а базовая функциональность была получена с минимальными усилиями, что и является преимуществом использования инфраструктуры Web Forms.

В этой и последующих статьях мы создадим то же самое приложение заново, но на этот раз построим более надежную основу, позаимствовав шаблоны, инструменты и приемы из других стилей разработки веб-приложений, в первую очередь - из ASP.NET MVC Framework. Инфраструктура MVC Framework делает особый акцент на проектировании модульных приложений, которые легко сопровождать и тестировать - и эти характеристики нам нужны в приложениях Web Forms.

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

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

Описание проблемы

Проблема, которой мы хотим избежать, связана с беспорядком кода, распространяющимся на все приложение Web Forms. В эту ловушку легко попасть из-за высокой гибкости кода веб-форм и классов отделенного кода. В примере ниже приведено содержимое файла отделенного кода Default.aspx.cs, которое было создано ранее:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.ModelBinding;

namespace TestAspNet45
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (IsPostBack)
            {
                GuestResponse rsvp = new GuestResponse();

                if (TryUpdateModel(rsvp, new FormValueProvider(ModelBindingExecutionContext)))
                {
                    ResponseRepository.GetRepository().AddResponse(rsvp);
                    if (rsvp.WillAttend.HasValue && rsvp.WillAttend.Value)
                    {
                        Response.Redirect("seeyouthere.html");
                    }
                    else
                    {
                        Response.Redirect("sorryyoucantcome.html");
                    }
                }
            }
        }
    }
}

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

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

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

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

Ситуация еще более ухудшается, когда мы начинаем применять фрагменты кода, содержащие операторы C#, как это делалось внутри файла Summary.aspx. Мы не собираемся перечислять все проблемные места кода, поскольку уже должно быть ясно, что приложения Web Forms могут стать довольно запутанными даже в таких простых случаях, как продемонстрированный ранее пример.

Описание решения

Принятый нами подход к структуризации приложений предусматривает использование шаблона под названием MVP (Model-View-Presenter - "модель-представление-презентатор"), который также известен как шаблон Supervising Controller (Координирующий контроллер). Он является вариацией шаблона MVC (Model-View-Controller - "модель-представление-контроллер"), применяемого в инфраструктуре MVC Framework, который настроен на использование в приложениях Web Forms. Мы применяем упрощенную версию этого шаблона, которая обеспечивает разделение различных частей приложения, не требуя слишком большой дополнительной структуры. Приложение разбивается на ряд частей, как показано на рисунке ниже:

Компоненты шаблона MVP

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

Ответственность компонентов приложения
Компонент Ответственность От чего изолирован
Представление

Получает запрос из браузера и применяет презентатор для выполнения бизнес-логики, что позволяет сгенерировать ответ клиенту

От бизнес-логики и модели данных/хранилища

Презентатор

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

От деталей, связанных с обработкой и форматом запроса/ответа, а также от подробностей хранения объектов данных в хранилище

Модель/хранилище

Сохраняет и извлекает объекты модели данных

Ничего не знает о браузерных запросах, ответах и бизнес-логике

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

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

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

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

Почему бы просто не воспользоваться MVC?

Вас может удивить, зачем мы пытаемся привнести идеи из MVC Framework в Web Forms - в конце концов, почему бы просто не переписать приложение с применением MVC?

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

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

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

Остерегайтесь фанатичных приверженцев шаблонов

В последующих статьях, связанных с модульным тестированием в ASP.NET Web Forms, мы принимаем ряд распространенных шаблонов проектирования программного обеспечения и показываем, как их использовать при разработке приложений Web Forms наряду с другими полезными инструментами. Мы не предполагаем, что это единственный способ создания приложений Web Forms или что каждый проект выиграет от применения описанных здесь приемов. Вместо этого мы покажем, как сами разрабатываем приложения Web Forms, и постараемся объяснить, что мы считаем преимуществами. Мы хотим, чтобы вы самостоятельно выбрали описанные здесь приемы и привели их в соответствие со своими потребностями.

К сожалению, в мире разработки программного обеспечения есть много людей, которые считают шаблоны "священными" и будут настаивать на скрупулезном следовании шаблонам вплоть до самых мелочей. Это не имеет никакого смысла, и вы должны игнорировать таких людей. Шаблоны фиксируют полезные идеи и выражают их так, что они могут многократно использоваться и адаптироваться; ключевое слово здесь - адаптироваться.

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

Пройди тесты
Лучший чат для C# программистов