在哪里放置业务逻辑?域模型和单元测试

时间:2014-09-29 13:24:12

标签: unit-testing design-patterns domain-driven-design strategy-pattern

我正在开发一个计算复杂系统成本的应用程序(C#)。该系统由许多部分(和子部分)组成,每个部分可能有不同的计算算法。最初,我计划创建一组仅包含属性作为系统部件表示的类。除了这些课程,我还会创建一套专用的成本计算器。它类似于程序编程和贫血领域模型(反模式?)。所以我想到了另一种方法:在那些部分实体中实现成本计算逻辑。但是单元测试会更加困难,因为"更高"零件取决于"更低"份'成本。所以基本上我有这两个选项(假设我的系统是披萨):

a)外部服务中的计算逻辑。贫血领域模型:

public class Pizza
{
    public List<BaseIngredient> BaseIngredients { get; set;}
    public List<ExtraIngredient> ExtraIngredients {get;set;}
    public List<Sauce> Sauces { get; set;}
}

public class PizzaCostCalculator : IPizzaCostCalculator
{
    private IBaseIngredientCostCalculator _baseIngredientCostCalculator;
    private ISauceCostCalculator _sauceCostCalculator;
    public PizzaCostCalculator(IBaseIngredientCostCalculator baseIngredientCostCalculator, ISauceCostCalculator sauceCostCalculator)
    {
        _baseIngredientCostCalculator = baseIngredientCostCalculator;
        _sauceCostCalculator = sauceCostCalculator;
    }

    public double GetCost(Pizza pizza)
    {
        double result = 0;
        result += _baseIngredientCostCalculator.GetCost(pizza.BaseIngredients)
        result += _sauceCostCalculator.GetCost(pizza.Sauces)
        // repeat above for extra ingredients etc

        result = ApplyDiscount(pizza);

        return result;
    }
}

b)计算实体内部的登录(如何将披萨与其成分分开进行单元测试?)

public class Pizza
{
    public List<BaseIngredient> BaseIngredients { get; set;}
    public List<ExtraIngredient> ExtraIngredients {get;set;}
    public List<Sauce> Sauces { get; set;}

    public double GetCost()
    {
        double result = 0;
        foreach (var baseIngredient in BaseInredient)
        {
           result += baseIngredient.GetCost();
        }

        // repeat above for sacues, extra ingredients, etc

        result = ApplyDiscount(result);

        return result;
    }
}

这两种方法的优点和缺点是什么?如果第二个更好,我应该如何进行单元测试呢?

1 个答案:

答案 0 :(得分:3)

您可以通过提供模拟成分来单独测试Pizza的成分。您的模型公开属性以执行此操作:

public List<BaseIngredient> BaseIngredients { get; set;}
public List<ExtraIngredient> ExtraIngredients {get;set;}
public List<Sauce> Sauces { get; set;}

&#34;安排&#34;单元测试的步骤将为这些具有预定义行为的对象创建模拟,在Pizza实例上设置它们,并调用正在测试的逻辑。

当然,另一种看待这种情况的方法是询问您是否需要分别测试Pizza成分。有一个&#34;纯粹主义者&#34;视图暗示&#34;每个班级必须独立测试&#34;但是,真的有多必要?如果成分是独立使用的,那么你当然可以为它们创建独立的测试。但看起来Pizza是一个由其他模型组成的聚合根模型。它的测试可以反映出来。

除非有外部依赖项进行模拟,否则编写单个测试(或一组测试)是完全可以接受的,这些测试运行Pizza上的计算具有有效成分实例的对象。成分的内部结构将同时进行间接测试。在这种情况下,您实质上正在测试的是系统的面向外部的业务逻辑,而不是系统的每个单独的技术组件。两者都有效。

如果您将测试与实施过于紧密联系,那么您会发现测试难以维护。如果业务运营只关心Pizza成本并且明确对如何执行该计算感兴趣,那么完全有效的测试就是测试一个顶级业务操作