如何对在 switch 语句中创建多个对象的方法进行单元测试?如何模拟它们?

How to unit test a method that is having multiple object creation in switch statement? How to Mock them?

另外一个问题就是这个方法有没有更好的写法?

Public decimal CalculateTotalPrice(List<product> items)
{
    decimal totalPrice = 0.m;

    foreach(Product p in items)
    {
        if(p.Offer == "")
            calc = new DefaultCalc();
        else if(p.Offer == "BuyOneGetOneFree")
            calc = new BuyOneGetOneFreeCalc();
        else if(p.Offer == "ThreeInPriceOfTwo")
            calc = new ThreeInPriceOfTwoCalc()

        totalPrice += calc.Calculate(p.Quantity, p.UnitPrice);
    }
    return totalPrice;
}

无法模拟构造函数(至少使用免费的模拟框架)。

就您的测试而言,无需模拟即可编写测试 运行 速度快,测试用例设置也不是非常非常复杂。

在您的特定情况下,您应该能够在不进行模拟的情况下编写测试。

准备数据

var products = new List<Product>
{
    new Product { Quantity = 10, UnitPrice = 5.0m, Offer = "" },
    new Product { Quantity = 2, UnitPrice = 3.0m , Offer = "BuyOneGetOneFree" },
    new Product { Quantity = 3, UnitPrice = 2.0m , Offer = "ThreeInPriceOfTwo" },
}

// prepare expected total
var expected = 57.0m; // 10 * 50.0 + 1 * 3.0 + 2 * 2.0

// Run the test
var actual = CalculateTotalPrice(products);

actual.Should().Be(expected); // pass Ok.

使用这种方法,测试将不依赖于实现细节。
每次更改实现逻辑时,您都可以自由地进行设计,而无需重写测试。

您可能应该查看 Sandi Metz 的 Polly Want a Message

How to unit test a method that is having multiple object creation in switch statement?

这里需要注意的一件重要事情是 switch 语句是一个实现细节。从调用者的角度来看,这个东西只是一个函数

Public decimal CalculateTotalPrice(List<product> items);

如果定价计算是固定的,您可以只使用通常的基于示例的测试:

assertEquals(expectedPrice, CalculateTotalPrice(items));

但如果它们不固定,您仍然可以根据方法的属性进行测试。 Scott Wlaschin 的表现非常出色 introduction to property based testing。根据您在此处展示的逻辑,我们可以在不了解所用策略的情况下就价格做出一些承诺

  • 价格应始终大于零。
  • 项目列表的价格与单个项目的价格总和相同。

if there is any better way to write this method?

您可以将选择定价策略与使用策略分开。正如 Sandi 指出的那样,这种结构经常出现在不止一次的地方。

foreach(Product p in items)
{
    calc = pricing(p.Offer);
    totalPrice += calc.Calculate(p.Quantity, p.UnitPrice);
}

"pricing" 将成为您传递给此函数的内容(作为参数或依赖项)。

实际上,您最终会进行三种不同的测试。

  • 检查每个报价的定价 returns 正确的定价策略。
  • 检查每个策略是否正确执行自己的计算。
  • 检查 CalculateTotalPrice 计算总和是否正确。

就我个人而言,我更喜欢将测试对象视为一个单独的大黑盒子,但是有good counter arguments。课程用马。

其他答案在技术上都很好,但我建议一件事:

    if(p.Offer == "")
        calc = new DefaultCalc();
    else if(p.Offer == "BuyOneGetOneFree")
        calc = new BuyOneGetOneFreeCalc();
    else if(p.Offer == "ThreeInPriceOfTwo")
        calc = new ThreeInPriceOfTwoCalc()

应该绝对进入它自己的method/scope/whatever。

您正在将字符串映射到特定的计算器。那应该发生在一个地方,而且只发生在一个地方。你看,首先你在这里做。然后出现一些需要相同映射的方法方法。所以你开始复制。