3

我有一个方法,其中有很多条件:

public bool IsLegalSomething(Order order)
{
    var item0 = order.Items.SingleOrDefault(x => x.ItemCode == "ItemCode0");
    var item1 = order.Items.SingleOrDefault(x => x.ItemCode == "ItemCode1");
    ...
    var itemN = order.Items.SingleOrDefault(x => x.ItemCode == "ItemCodeN");

    return ((item0.Status == Status.Open) && (item1.Status == Status.Closed)
         && ...
         && (itemN.Status == Status.Canceled));
}

我想对这个函数进行单元测试,但是条件太多了,如果你考虑每一个组合,单元测试的数量就会很疯狂。该返回语句中有 16 个条件,并且由于每个条件都是真/假,即 2^16 种不同的组合,我需要检查。我真的需要在这里创建 2^16 个不同的单元测试来确保每个条件都被利用吗?请注意,这是一个简单的例子。由于法律要求,我的一些功能具有复杂的条件:

return (condition0 && condition1 && (condition2 || condition3)
     && (condition4 || (condition5 && condition6)) ...)

根据我的一些函数的数学计算,条件可以产生的不同组合的数量是数百万!我研究了数据驱动单元测试 (DDUT) 以及参数化单元测试 (PUT),但这只是让单元测试成为“填空”风格。我仍然必须提供所有各种组合和预期的结果!例如:

// Parameterized Unit Test
[TestCase(..., Result = true)]  // Combination 0
[TestCase(..., Result = true)]  // Combination 1
[TestCase(..., Result = false)] // Combination 2
public bool GivenInput_IsLegalSomething_ReturnsValidResult(...) { }

如果我使用 MSTest 来提取数据源(例如 csv),我仍然会遇到同样的问题。我有太多的组合会产生不同的结果。有没有我不知道的替代方案?

4

2 回答 2

1

虽然我同意关于重构代码的评论,但我认为有必要提供更简洁的答案来解释“设计可能需要审查和重构”的确切含义。

让我们看看下面的声明:A && B && (C || D);. 通常,您会说您有 4 个输入 @ 2 个选项/每个,或16 个组合。但是,如果你重构一些东西,你可以降低复杂性。这将根据您的业务域而有所不同,因此我将使用购物网站域作为示例(我们实际上并不知道您的业务域)。

  • A: 是新订单吗
  • B:是否所有商品都有库存
  • C: 是客户精英会员
  • D:是否使用折扣码 FREESHIP

我选择这个场景的原因是为了证明 C/D 实际上可能会提到是否应该免费包含运费。

  • E: 是否免运费

现在,而不是A && B && (C || D)我们有A && B && E3 个条件@2 个选项/每个,或8 个组合。当然,E的组成也应该测试,但C || D只有2个选项@2个选项/每个,或4个组合。我们已将总组合的数量从 16 个减少到 12 个。虽然这可能看起来不多,但这种情况的规模要小得多。如果您能够将逻辑条件组合在一起并进一步减少事物,则可以从数百万个组合减少到数百个,这更容易维护。

此外,作为奖励,有时您的域逻辑在某些方面会发生变化,但在其他方面则不会。想象一下,您决定商业客户也可以在某一天获得免费送货,而不是在极其复杂的条件语句中添加另一个条件,基本上将单元测试的数量增加一倍,而是将该条件添加到Is Free Shipping,这将增加数量该较小单元的组合从 4 到 8,但它比具有 16 个条件(即 65536 个组合)到 17 个条件(即 131072 个组合)的函数要好。


在这一点上,除非我们知道并了解您的确切领域,否则我们只能提出将您的类和方法重新设计成更小的部分的广泛建议。此外,虽然mart使用字符串长度 > 5 不需要测试每个长度大于 5 的字符串,但我确实认为一旦将实际条件降低为真/假,就需要测试组合条件。例如:(String Length > 5) && B && C && D && E有 5 个条件或 32 种组合。你应该测试所有 32 个。你不应该做的是想出 100 种不同的方法来证明 String Length > 5 是正确的。我说要测试所有 32 种组合的原因是因为虽然可以重构和测试条件,但您仍然想测试您正在使用的A && B && (C || D),因此您可以确定没有人打错A && B || (C && D). 或者,换句话说,证明单个条件并不能保证这些条件的组合被正确编码。

于 2017-01-25T21:56:48.487 回答
0

您应该重构代码以正确测试它,每个条件块本身应该是一个函数,并且该函数应该单独测试,然后您的主函数只需要测试来检查集成。

单元测试并不意味着涵盖所有选项,事实上,如果您正在测试 true && true、false && false 等等,您实际上是在测试 && 运算符而不是您的逻辑,您应该涵盖您的功能作为“单元”不是所有可能的输入组合,只是想在一个检查字符串长度是否大于 5 的函数中,你会创建世界上所有可能的字符串吗?或者你只是要测试一个等于 5,一个超过 5 个字符,一个更低,也许你也应该测试 null,但不要超过这个。

无论如何,只是为了给你一个真正的替代方案,如果你能够将 NUnit 添加到你的项目中,你可以使用https://github.com/nunit/docs/wiki/Combinatorial-Attribute来生成所有这些测试你要的那个。

于 2017-01-25T18:29:49.957 回答