ЖАНРЫ

Шрифт:

public ActionResult Index

{

ViewData["Message"] = "Всем привет!";

return View;

}

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

отображено сообщение об ошибке,

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

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

Несколько слов о важности тестирования

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

Примечание

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

*****************************

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

Тесты и MVC Framework

После прочтения предыдущей части главы можно было убедиться, что создание теста логики веб-приложения, построенного на базе MVC Framework, — задача простая. Этот эффект достигается за счет того, что ядро веб-приложения, состоящее из контроллеров и слоя работы с данными (моделей), может быть выделено в независимую от среды сборку и тестироваться вне контекста веб-сервера и без генерации разметки представления. Даже в тех случаях, когда среде выполнения необходимо взаимодействие с объектами, отвечающими за ввод/вывод разметки и запросов, передаваемых между пользователем и сервером, используются абстрактные классы (например, HttpRequestBase), реализация которых может быть подменена в тестовой среде.

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

или функциональности, использование которой в тестовых условиях не требуется для проведения тестов.

Для поддержки возможности замены конкретной реализации необходимо использовать интерфейсы для основных зависимостей действий контроллеров. Например, если в приложении есть выделенный объект, отвечающий за восстановление и сохранение объектов данных DataManager, то в контроллерах следует использовать интерфейс iDataManager, который реализует конкретный класс DataManager. Таким образом, во время проведения тестов объект DataManager может быть подменен объектом DataManagerMock, не содержащим полного функционала работы с данными и, возможно, даже не работающим с базой данных. Этот распространенный подход к тестированию называется имитацией (англ. moking).

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

Рассмотрим создание более сложного теста на примере тестового сценария для контроллера Accountcontroller, входящего в шаблон MVC-приложения. В листинге 8.2 приведен фрагмент кода Accountcontroller, демонстрирующий использование интерфейсов в конструкторах класса.

Листинг 8.2. Фрагмент кода класса Accountcontroller

public class Accountcontroller : Controller

{

public Accountcontroller : this(null, null)

{

}

public AccountController(IFormsAuthentication formsAuth,

IMembershipService service)

{

FormsAuth = formsAuth ?? new FormsAuthenticationService;

MembershipService = service ?? new AccountMembershipService;

}

public IFormsAuthentication FormsAuth

{

get;

private set;

}

public IMembershipService MembershipService

{

get;

private set;

}

}

Поскольку в контроллере Accountcontroller используются компоненты инфраструктуры ASP.NET, отвечающие за аутентификацию пользователей и управление учетными записями, для них созданы специальные обертки, приведенные в листинге 8.3, реализующие интерфейсы IFormsAuthentication и IMembershipService.

Листинг 8.3. Интерфейсы и реализация поддержки базовых служб

public interface IFormsAuthentication

{

void SignIn(string userName, bool createPersistentcookie);

void SignOut;

}

public class FormsAuthenticationService : IFormsAuthentication

{

public void SignIn(string userName, bool createPersistentCookie)

{

FormsAuthentication.SetAuthCookie(userName,

createPersistentCookie);

}

public void SignOut

{

FormsAuthentication.SignOut;

}

}

public interface IMembershipService

{

int MinPasswordLength { get; }

bool ValidateUser(string userName, string password);

MembershipCreateStatus CreateUser(string userName,

string password, string email);

bool ChangePassword(string userName,

string oldPassword, string newPassword);

}

public class AccountMembershipService : IMembershipService

{

private MembershipProvider _provider;

Поделиться с друзьями: