I am developing an application (C#) which calculates the cost of a complex system. This system consists of many parts (and sub-parts) and each part may have different calculation algorithms. Initially, I planned to create a set of classes containing only properties as a representation of system parts. In addition to these classes I would create a set of dedicated cost calculators. It would resemble procedural programming and anemic domain model (antipattern?). So I thought of another way: implementing cost-calculating logic inside those part-entities. But then unit testing would be more difficult because "higher" parts depends on "lower" parts' cost. So basically I have these two options (assuming my system is a pizza):
a) Calculation logic in external services. Anemic domain model:
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) Calculation login inside entities (how to unit test pizza in separation from its ingredients ?)
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;
}
}
What are pros and cons of both approaches? If the second one is better how should I unit-test it?
You can test Pizza
separately from its ingredients by supplying mock ingredients. Your model exposes properties to do this:
public List<BaseIngredient> BaseIngredients { get; set;}
public List<ExtraIngredient> ExtraIngredients {get;set;}
public List<Sauce> Sauces { get; set;}
The "arrange" step of the unit test would create mocks for these objects with pre-defined behaviors, set them on the Pizza
instance, and invoke the logic being tested.
Of course, another way to look at this is to ask if you need to test Pizza
separately from the ingredients. There's a "purist" view which implies that "every class must be tested independently" but how necessary is that, really? If ingredients are ever used independently then you can certainly create independent tests for them. But it looks like Pizza
is an aggregate root model which is composed of other models. Its test can reflect that.
Unless there are external dependencies to mock, it's perfectly acceptable to write a single test (or set of tests) which runs the calculation(s) on the Pizza
object with valid instances of ingredients. The internals of the ingredients would be indirectly tested at the same time. What you're essentially testing in this case is the external-facing business logic of the system, as opposed to each individual technical component of the system. Both are valid.
If you couple your tests too tightly with your implementation then you'll find the tests difficult to maintain. If the business operations only ever care what a Pizza
costs and aren't explicitly interested in how that calculation is performed then a perfectly valid test is to test that one top-level business operation.