有没有更简单的方法来处理单元测试条件太多的方法?

Is there an easier way to handle unit testing a method with too many conditions?

我有一个方法,里面有很多条件:

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));
}

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

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

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

// 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),我仍然遇到同样的问题。我有太多的组合会产生不同的结果。有没有我不知道的替代方案?

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

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

无论如何,如果您能够将 NUnit 添加到您的项目中,您可以使用 https://github.com/nunit/docs/wiki/Combinatorial-Attribute 来生成您想要的所有测试。

虽然我同意关于重构代码的评论,但我认为有必要提供更简洁的答案来解释 "The design may need to be reviewed and refactored."[=18 的确切含义=]

我们来看下面的语句:A && B && (C || D);。通常,您会说您有 4 个输入 @ 2 options/each,或 16 种组合 。但是,如果你重构事物,你可以降低复杂性。这将根据您的业务领域而有所不同,因此我将以购物网站域为例(我们实际上不知道您的业务领域)。

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

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

  • E:免运费

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

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


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