Как проверить защищенный абстрактный метод абстрактного класса? - PullRequest
3 голосов
/ 20 октября 2010

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

Там, где у меня были некоторые проблемы, выясняется, как проверить метод OnActionExecuted базового класса. Этот метод основан на реализации защищенного абстрактного метода GetCustomer. Я пытался издеваться над классом, используя Rhino Mocks , но не могу посмеяться над возвращением поддельного клиента из GetCustomer. Очевидно, что переключение метода на public сделает насмешку доступной, но protected кажется более подходящим уровнем доступности .

В настоящее время в моем тестовом классе я добавил конкретный закрытый класс, который наследуется от TabsActionFilter и возвращает поддельный объект Customer.

  • Является ли конкретный класс единственным вариантом?
  • Есть ли простой механизм насмешек, который мне не хватает, который позволил бы Rhino Mocks обеспечить возврат GetCustomer?

Как примечание Андерсон Аймс обсуждает свое мнение по этому вопросу в ответе по поводу 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 ]

2 голосов
/ 20 октября 2010

Я думаю, что проблема, с которой вы сейчас сталкиваетесь, заключается в том, что ваш класс не тестируемый или недостаточно тестируемый.Это, конечно, при условии, что вы правильно определили, что GetCustomer действительно является чем-то, что необходимо смоделировать для правильного тестирования в изоляции.

Если GetCustomer - это то, что нужно смоделировать для правильной изоляции и тестированияTabsActionFilter, вам как-то нужно будет сделать реализацию GetCustomer составной частью класса, а не унаследованным методом.Самый распространенный способ добиться этого - использовать Inversion of Control / Dependency Injection.

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

(я не фанатиспользования TypeMock FWIW).

1 голос
/ 20 октября 2010

Фил ответ правильный.Если бы вместо абстрактного класса у вас был класс, который требовал внедрения клиентского геттера (или фабрики, или чего-то еще), то вы были бы в хорошей форме для тестирования.Абстрактные классы являются врагами тестирования (и хорошего дизайна).

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, который должен быть тривиальным для mock.

РЕДАКТИРОВАТЬ: кажется, есть опасение, что вы должны иметь конструктор без параметров.это просто.учитывая приведенный выше код, у вас тогда будут реализованы ваши «настоящие» фильтры как

public class MyFilter : TabsActionFilter
{
    public MyFilter() : base(new MyGetter()) {}
}

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

...