【问题标题】:unit testing a factory method对工厂方法进行单元测试
【发布时间】:2012-03-07 17:31:50
【问题描述】:

假设我有几个OrderProcessors,他们每个人处理订单的方式都略有不同。
关于使用哪个OrderProcessor 的决定是根据Order 对象的属性来完成的,并由工厂方法完成,如下所示:

public IOrderProcessor CreateOrderProcessor(IOrdersRepository repository, Order order, DiscountPercentages discountPercentages)
{
    if (order.Amount > 5 && order.Unit.Price < 8)
    {
        return new DiscountOrderProcessor(repository, order, discountPercentages.FullDiscountPercentage);
    }

    if (order.Amount < 5)
    {
        // Offer a more modest discount
        return new DiscountOrderProcessor(repository, order, discountPercentages.ModestDiscountPercentage);
    }

    return new OutrageousPriceOrderProcessor(repository, order);
}

现在,我的问题是我想验证返回的OrderProcessor 是否收到了正确的参数(例如,正确的折扣百分比)。
但是,这些属性在 OrderProcessor 实体上并不公开。

您建议我如何处理这种情况?

我能想出的唯一解决方案是将OrderProcessors 的折扣百分比属性公开,但仅出于单元测试的目的这样做似乎有点矫枉过正......

【问题讨论】:

    标签: unit-testing nunit moq


    【解决方案1】:

    解决此问题的一种方法是将要测试的字段更改为内部字段而不是私有字段,然后将项目的内部设置为对测试项目可见。你可以在这里阅读:http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx

    你会在你的 AssemblyInfo.cs 文件中做这样的事情:

    [assembly:InternalsVisibleTo("Orders.Tests")]
    

    尽管您可能会争辩说您的单元测试不一定要关心您班级的私有字段。当在接口上调用某些方法(假设 Calculate() 或类似的东西)时,也许最好将值传递给工厂方法并为预期结果编写单元测试。

    或者另一种方法是对具体类型(DiscountOrderProcessor 等)进行单元测试,并从公共方法/属性确认它们的返回值。然后为工厂方法编写单元测试,使其正确返回正确类型的接口实现。

    这些是我在编写类似代码时通常采用的方法,但是有很多不同的方法可以解决这样的问题。我建议弄清楚在单元测试中你会在哪里获得最大的价值并据此编写。

    【讨论】:

      【解决方案2】:

      如果折扣百分比不公开,则它不是 IOrderProcessor 合同的一部分,因此不需要验证。只需对 DiscountOrderProcessor 进行一组单元测试,以验证它是否根据通过构造函数传入的折扣百分比正确计算您的折扣。

      【讨论】:

      • 虽然这很好,但想知道正在使用正确的折扣百分比似乎是合理的,否则有人可以更改代码以使两个顶级路径都返回 discountPercentages.FullDiscountPercentage 而没有人会知道,突然之间人们会开始获得全额折扣,而不是适度的折扣
      • 好的,将 DiscountOrderProcessor 的创建放在一个虚拟方法中,并确保根据您的工厂逻辑使用适当的参数调用它。
      • 我不得不同意帕特里克的观点。听起来您正在冒险从单元测试进入集成测试(这也很重要)。单元测试是针对一个逻辑单元的,每个订单处理器都应该有自己的单元测试。
      【解决方案3】:

      在我看来,你有几个选择。您可以创建 DiscountOrderProcessor 的专业化:

      public class FullDiscountOrderProcessor : DiscountOrderProcessor
      {
          public FullDiscountOrderProcessor(IOrdersRepository repository, Order order):base(repository,order,discountPercentages.FullDiscountPercentage)
          {}
      }
      
      public class ModestDiscountOrderProcessor : DiscountOrderProcessor
      {
          public ModestDiscountOrderProcessor (IOrdersRepository repository, Order order):base(repository,order,discountPercentages.ModestDiscountPercentage)
          {}
      }
      

      并检查返回的类型是否正确。

      你可以传入一个工厂来创建只需要一个金额的 DiscountOrderProcessor,然后你可以检查它是否使用正确的参数调用。

      您可以提供一个虚拟方法来创建 DiscountOrderProcessor 并检查是否使用正确的参数调用。

      我个人非常喜欢第一个选项,但是所有这些方法都存在相同的问题,即最终您无法检查实际值,因此有人可能会更改您的折扣金额而您不知道。即使使用第一种方法,您最终也无法测试应用于 FullDiscountOrderProcessor 的值是什么。

      您需要通过某种方式检查留给您的实际值:

      您可以将属性公开(或内部 - 使用 InternalsVisibleTo),以便查询它们。

      您可以获取返回的对象并检查它是否正确地将折扣应用于您传递给它的某些对象。

      我个人倾向于将属性设置为内部,但这取决于对象如何交互以及将模拟对象传递给折扣订单处理器并验证它是否正确执行很简单,那么这可能会更好解决方案。

      【讨论】:

        猜你喜欢
        • 2011-10-18
        • 2011-06-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-12
        • 2013-05-10
        • 1970-01-01
        • 2017-04-13
        相关资源
        最近更新 更多