Как проверить защищенный абстрактный метод абстрактного класса?
Я работал над лучшим способом тестирования абстрактного класса с именем TabsActionFilter
, Я гарантировал, что классы, которые наследуются от TabsActionFilter
будет иметь метод с именем GetCustomer
, На практике этот дизайн, кажется, работает хорошо.
Там, где у меня были некоторые проблемы, выясняется, как проверить OnActionExecuted
метод базового класса. Этот метод основан на реализации защищенного реферата GetCustomer
метод. Я пытался издеваться над классом, используя Rhino Mocks, но не могу посмеяться над возвращением поддельного клиента из GetCustomer
, Очевидно, что переключение метода на публичный доступ сделает насмешку доступной, но защищенный будет выглядеть как более подходящий уровень доступности.
В настоящее время в моем тестовом классе я добавил конкретный закрытый класс, который наследуется от TabsActionFilter
и возвращает поддельный объект Customer.
- Является ли конкретный класс единственным вариантом?
- Есть ли простой механизм насмешек, который мне не хватает, который позволил бы Rhino Mocks обеспечить возврат
GetCustomer
?
В качестве примечания Anderson Imes обсуждает свое мнение по этому поводу в ответе на вопрос о Moq, и я мог упустить что-то ключевое, но это здесь не применимо.
Класс, который должен быть проверен
public abstract class TabsActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
Customer customer = GetCustomer(filterContext);
List<TabItem> tabItems = new List<TabItem>();
tabItems.Add(CreateTab(customer, "Summary", "Details", "Customer",
filterContext));
tabItems.Add(CreateTab(customer, "Computers", "Index", "Machine",
filterContext));
tabItems.Add(CreateTab(customer, "Accounts", "AccountList",
"Customer", filterContext));
tabItems.Add(CreateTab(customer, "Actions Required", "Details",
"Customer", filterContext));
filterContext.Controller.ViewData.PageTitleSet(customer.CustMailName);
filterContext.Controller.ViewData.TabItemListSet(tabItems);
}
protected abstract Customer GetCustomer(ActionExecutedContext filterContext);
}
Тестовый класс и закрытый класс для "издевательства"
public class TabsActionFilterTest
{
[TestMethod]
public void CanCreateTabs()
{
// arrange
var filterContext = GetFilterContext(); //method omitted for brevity
TabsActionFilterTestClass tabsActionFilter =
new TabsActionFilterTestClass();
// act
tabsActionFilter.OnActionExecuted(filterContext);
// assert
Assert.IsTrue(filterContext.Controller.ViewData
.TabItemListGet().Count > 0);
}
private class TabsActionFilterTestClass : TabsActionFilter
{
protected override Customer GetCustomer(
ActionExecutedContext filterContext)
{
return new Customer
{
Id = "4242",
CustMailName = "Hal"
};
}
}
}
2 ответа
Я думаю, что проблема, с которой вы сейчас сталкиваетесь, заключается в том, что ваш класс не поддается тестированию или недостаточно тестируем. Это, конечно, при условии, что вы правильно определили, что GetCustomer действительно является чем-то, что необходимо смоделировать для правильного тестирования в изоляции.
Если для правильной изоляции и тестирования TabsActionFilter необходимо использовать макет GetCustomer, вам необходимо каким-то образом сделать реализацию GetCustomer составной частью класса, а не унаследованным методом. Наиболее распространенным способом достижения этого является использование Inversion of Control/Dependency Injection.
Все это говорит, что вы могли бы использовать что-то вроде TypeMock для достижения этой цели. Однако, когда вы сталкиваетесь с такой ситуацией, когда класс трудно протестировать, это, как правило, сигнал о том, что у вашего класса слишком много обязанностей и его нужно разбить на более мелкие компоненты.
(Я не фанат использования TypeMock FWIW).
Фил ответ правильный. Если бы вместо абстрактного класса у вас был класс, который требовал внедрения клиентского геттера (или фабрики, или чего-то еще), то вы были бы в хорошей форме для тестирования. Абстрактные классы - враг тестирования (и хорошего дизайна).
public class TabsActionFilter : ActionFilterAttribute
{
private readonly ICustomerGetter _getter;
public TabsActionFilter(ICustomerGetter getter)
{ _getter = getter; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
Customer customer = _getter.GetCustomer(filterContext);
...
}
}
public interface ICustomerGetter
{
Customer GetCustomer(ActionExecutedContext filterContext);
}
в своем тесте вы создаете экземпляр неабстрактного теперь TabsActionFilter и даете ему средство получения Mock, которое должно быть тривиальным для насмешки.
РЕДАКТИРОВАТЬ: кажется, есть проблема, что вы должны иметь конструктор без параметров. это просто. с учетом приведенного выше кода, ваши "настоящие" фильтры будут реализованы как
public class MyFilter : TabsActionFilter
{
public MyFilter() : base(new MyGetter()) {}
}
Вы все еще можете проверить свой базовый класс так же.