Как проверить защищенный абстрактный метод абстрактного класса?

Я работал над лучшим способом тестирования абстрактного класса с именем 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()) {}
}

Вы все еще можете проверить свой базовый класс так же.

Другие вопросы по тегам