如何对在 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。
您正在将字符串映射到特定的计算器。那应该发生在一个地方,而且只发生在一个地方。你看,首先你在这里做。然后出现一些需要相同映射的方法方法。所以你开始复制。
另外一个问题就是这个方法有没有更好的写法?
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。
您正在将字符串映射到特定的计算器。那应该发生在一个地方,而且只发生在一个地方。你看,首先你在这里做。然后出现一些需要相同映射的方法方法。所以你开始复制。