如何测试抽象类的受保护抽象方法?

时间:2010-10-20 18:18:06

标签: c# unit-testing tdd rhino-mocks mstest

我一直在研究测试名为TabsActionFilter的抽象类的最佳方法。我保证从TabsActionFilter继承的类将有一个名为GetCustomer的方法。在实践中,这种设计似乎运作良好。

我遇到的一些问题是如何测试基类的OnActionExecuted方法。此方法依赖于 protected abstract GetCustomer方法的实现。我曾尝试使用Rhino Mocks来模拟该类,但似乎无法模仿来自GetCustomer的虚假客户的回复。显然,将方法转移到 public 会使模拟可用,但 protected 感觉更合适accessibility level

暂时在我的测试类中,我添加了一个继承自TabsActionFilter的具体私有类,并返回一个伪造的Customer对象。

  • 具体类是唯一的选择吗?
  • 是否有一种简单的模拟机制,我错过了允许Rhino Mocks为GetCustomer提供回报?

请注意Anderson Imesanswer about 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 个答案:

答案 0 :(得分:2)

我认为您目前遇到的问题是您的课程不可测试,或者不够可测试。这当然是假设您已经正确识别出GetCustomer确实需要被模拟,以便能够单独进行正确测试。

如果为了正确隔离和测试TabsActionFilter而需要模拟GetCustomer,那么您将需要以某种方式使GetCustomer的实现成为类的可组合部分,而不是继承的方法。实现此目的的最常见方法是使用控制/依赖注入反转。

所有这一切,你 COULD 使用类似TypeMock的东西来实现这一目标。但是,当你遇到这样一种难以测试的类的情况时,通常会发出一个信号,表明你的类有太多的责任,需要分成更小的组件。

(我不喜欢使用TypeMock FWIW)。

答案 1 :(得分:1)

菲尔的回答是正确的。如果没有一个抽象类,你有一个类需要注入一个客户吸气剂(或工厂,或其他),那么你就可以进行良好的测试。抽象类是测试(和良好的设计)的敌人。

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 getter,这对于模拟应该是微不足道的。

编辑:似乎有人担心你必须有一个无参数构造函数。这很简单。鉴于上述代码,您可以将“真实”过滤器实现为

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

你仍然可以测试你的基类。