Asp.net mvc framework
Шрифт:
Примечание
Особенно важно автоматическое тестирование тех действий контроллеров, которые возвращают данные для последующей обработки в 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;
public AccountMembershipService
: this(null)
{
}
public AccountMembershipService(MembershipProvider provider)
{
_provider = provider ?? Membership.Provider;
}
public int MinPasswordLength
{
get
{
return _provider.MinRequiredPasswordLength;
}
}
public bool ValidateUser(string userName, string password)
{
return _provider.ValidateUser(userName, password);