在IF语句中对多个条件进行单元测试


26

我有一大堆看起来像这样的代码:

function bool PassesBusinessRules()
{
    bool meetsBusinessRules = false;

    if (PassesBusinessRule1 
         && PassesBusinessRule2
         && PassesBusinessRule3)
    {
         meetsBusinessRules= true;
    }

    return meetsBusinessRules;
}

我相信针对此特定功能应进行四个单元测试。三项测试if语句中的每个条件,并确保其返回false。另一项确保该函数返回true的测试。

问题:实际上应该有十个单元测试吗?九个检查每个可能的故障路径。IE浏览器:

  • 错误错误错误
  • 错误错误正确
  • 错误正确错误

对于每种可能的组合,依此类推。

我认为这太过分了,但是我团队中的其他一些成员却没有。我的看法是,如果BusinessRule1失败,那么它应该始终返回false,无论是首先检查还是最后检查都没有关系。


编译器是否对&&运算符使用贪婪求值?
suszterpatt

12
如果编写了10个单元测试,则将是测试&&运算符,而不是方法。
Mert Akcakaya 2012年

2
如果您测试了所有可能的组合,会不会只有八个测试?三个布尔参数打开或关闭。
克里斯·哈珀

3
@Mert:只有可以保证&&始终存在。
米斯科,2012年

Hickey:如果我们花时间在编写测试上,那时候就不用花时间做其他事情了。我们每个人都需要评估如何最好地度过时间,以便在数量和质量上最大化我们的结果。如果人们认为花费50%的时间来编写测试可以最大程度地提高测试结果-对他们来说还可以。我确定这对我来说不是真的-我宁愿花时间去思考自己的问题。我敢肯定,对于我来说,这将产生比其他任何时间都更好的解决方案,缺陷更少。具有完整测试套件的不良设计仍然是不良设计。
Job

Answers:


29

正式地,这些类型的承保范围有名称。

首先,有一个谓词覆盖:您希望有一个使if语句为true的测试用例,以及一个使它为false的测试用例。满足此覆盖范围可能是一个好的测试套件的基本要求。

然后是Condition Coverage:在这里,您要测试if中的每个子条件的值为true和false。显然,这会创建更多测试,但通常会捕获更多错误,因此,如果有时间的话,将其包含在测试套件中通常是个好主意。

最高级的覆盖范围标准通常称为“ 组合条件覆盖范围”:这里的目标是让测试用例通过测试中布尔值的所有可能组合

这比简单的谓词或条件覆盖更好吗?当然,就覆盖范围而言。但这不是免费的。它在测试维护中付出了很高的代价。因此,大多数人不会为完整的组合覆盖范围而烦恼。通常测试所有分支(或所有条件),足以捕获错误。为组合测试添加额外的测试通常不会捕获更多的错误,但是需要大量的精力来创建和维护。额外的努力通常会使这不值得非常小的回报,因此我不建议这样做。

此决定的一部分应基于您认为该代码的风险程度。如果有很大的失败空间,则值得进行测试。如果它比较稳定并且不会有太大变化,则应考虑将测试工作重点放在其他地方。


2
如果布尔值是从外部来源传递的(意味着它们并不总是得到验证),则通常需要组合条件覆盖。首先制作一张组合表。然后,对于每个条目,确定该条目是否代表有意义的用例。如果没有,应该在某处有代码(软件断言或验证子句)以防止执行该组合。重要的是不要在单个组合测试中汇总所有参数:尝试将参数划分为相互交互的组,即共享相同的布尔表达式。
rwong

您对这些粗体字有多确定?您的答案似乎是“组合条件覆盖率”的唯一出现,并且一些资源说“谓词覆盖率”和“条件覆盖率”是同一回事。
Stijn

8

最终,它取决于您(团队),代码和特定的项目环境。没有普遍的规则。您(团队)应该编写尽可能多的测试,以使自己确信代码确实正确。因此,如果您的队友没有通过4次测试说服,也许您需要更多。

OTOH编写单元测试的时间通常是稀缺资源。因此,努力寻找最佳的方式来度过有限的时光。例如,如果您有另一种覆盖率为0%的重要方法,则最好编写几个单元测试来覆盖该方法,而不是为此方法添加额外的测试。当然,这也取决于每个实现的脆弱性。在可预见的将来计划对此特定方法进行很多更改可能会证明额外的单元测试覆盖范围是合理的。因此可能处于程序内部的关键路径上。这些都是只有您(团队)可以评估的因素。

我个人通常会对您概述的4个测试感到满意,即:

  • 真假假
  • 假真假
  • 假假真
  • 真实真实真实

再加上一个:

  • 真真假

确保获得返回值的唯一方法true是满足所有3个业务规则。但是最后,如果您的队友坚持要涵盖组合路径,那么添加这些额外的测试可能比继续争论更长的时间更便宜:-)



2

是的,在理想的世界中应该有完整的组合。

在做单元测试,你确实应该忽视如何的方法做的工作。只需提供3个输入并验证输出正确即可。


1
单元测试白盒测试。而且我们没有生活在理想的世界中。
彼得Török

@PéterTörök -我们不是生活在一个理想的世界是肯定的,但stackexchange与你的另一点异议。特别是对于TDD,测试是按照规范而不是实现编写的。我个人认为“规范”包括所有输入(包括成员变量)和所有输出(包括副作用)。
Telastyn

1
在特定情况下,这只是StackOverflow上的一个特定线程,不应过度概括。特别是由于当前这篇文章显然是关于已经编写的测试代码的。
彼得Török

1

国家是邪恶的。以下功能不需要进行单元测试,因为它没有副作用,并且可以很好地理解其作用和不作用。为什么要测试?你不相信自己的大脑吗???静态功能很棒!

static function bool Foo(bool a, bool b, bool c)
{
    return a && b && c;
}

2
不,我不相信自己的大脑-我学会了始终反复检查自己所做工作的艰难方法:-)因此,我仍然需要进行单元测试,以确保我没有例如打错任何东西,而且没有人去将来破坏代码。和更多的单元测试,以验证其计算由下式表示的状态下的呼叫者的方法abc。您可以随意移动业务逻辑,最后仍然需要在某处进行测试。
彼得Török

@PéterTörök,您也可以在测试中输入错误,从而导致误报,所以您在哪里停下来?您是否为单元测试编写单元测试?我也不是100%相信我的大脑,但是最终,编写代码是我的谋生手段。该函数中可能存在错误,但以这样的方式编写代码很重要,即使错误易于跟踪到源代码,因此一旦找出问题并进行了修复,您的处境会更好。编写良好的代码大部分都可以依赖集成测试infoq.com/presentations/Simple-Made-Easy
Job

2
确实,测试也可能是错误的。(TDD通过首先使测试失败来解决此问题。)但是,两次产生相同类型的错误(并忽略该错误)的可能性要低得多。通常,没有任何数量和类型的测试可以证明该软件没有错误,只是错误的可能性降低到可接受的水平。而在追查错误的源的速度,IMO没有什么可以打败单元测试-快速反馈rulez :-)
彼得Török

“以下功能不需要单元测试”我想您在这里很讽刺,但是还不清楚。我相信自己的大脑吗?没有!我相信下一个接触代码的人的大脑吗?甚至没有!我是否相信准则背后的所有假设都将在一年后成为现实?...让我流连忘返。另外,静态函数会杀死OO ...如果您想执行FP,请使用FP语言。
罗布

1

我知道这个问题已经很老了。但是我想换一个角度来看这个问题。

首先,您的单元测试应具有两个目的:

  1. 为您和您的队友创建文档,因此在给定的时间后,您可以阅读单元测试并确保您了解what's the class' intentionhow the class is doing its work
  2. 在开发过程中,单元测试可确保我们正在编写的代码能够按照我们的预期进行。

因此,概括这个问题,我们要测试a complex if statement,对于给定的示例,有2 ^ 3种可能性,这是我们可以编写的大量测试。

  • 您可以适应这一事实并写下8个测试或使用参数化测试
  • 您还可以按照其他答案进行操作,并记住测试应该是有目的的,这样我们就不会把太多的细节弄得乱七八糟,以至于在不久的将来可能会使人难以理解 what is doing the code

另一方面,如果您的测试比实现更复杂,那是因为应该重新设计实现(或多或少取决于情况),而不是测试本身。

例如,对于复杂的if语句,您可以考虑连锁责任模式,以这种方式实现每个处理程序:

If some simple business rule apply, derive to the next handler

测试各种简单规则而不是复杂规则会多么简单?

希望能帮助到你,


0

这是其中诸如quickcheck(http://en.wikipedia.org/wiki/QuickCheck)之类的东西将成为您朋友的情况之一。而不是手工写出所有N个案例,而是让计算机生成所有(或至少大量)可能的测试案例,并验证所有案例均返回有意义的结果。

我们为在这里生活的计算机编程,为什么不对计算机编程以为您生成测试用例?


0

您可以将条件重构为警戒条件:

if (! PassesBusinessRule1) {
    return false;
}

if (! PassesBusinessRule2) {
    return false;
}

if (! PassesBusinessRule3) {
    return false;
}

我不认为这样可以减少案件数量,但我的经验是,以这种方式进行处理更容易。

(请注意,我是一个很大的“单一出口点”风扇,但是我确实将警卫条件视为例外。但是还有其他方法来构造代码,因此您没有单独的收益。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.