代码之家  ›  专栏  ›  技术社区  ›  lhumongous

在具有单元测试的C++程序中,断言的作用是什么?

  •  14
  • lhumongous  · 技术社区  · 16 年前

    我已经为一些传统C++代码添加了单元测试,并且我遇到了许多情况,其中一个函数内的断言在单元测试运行期间会被跳过。我遇到的一个常见习惯用法是,函数接受指针参数并在参数为NULL时立即断言。

    另一方面,我不喜欢在代码中添加软失败(例如。 if (param == NULL) return false; ). 运行时断言至少使调试问题变得更容易,以防单元测试遗漏了一个bug。

    10 回复  |  直到 15 年前
        1
  •  18
  •   Orion Edwards    16 年前

    运行时断言至少使调试问题变得更容易,以防单元测试遗漏了一个bug。

    这是一个非常基本的观点。单元测试并不是要取代断言(断言是产生高质量代码的标准部分),而是要补充断言。

    第二,假设你有一个函数 Foo 它断言它的参数是有效的。
    在你的单元测试中 你可以确保你只提供有效的参数,所以你认为你是好的。
    福 从一些新的代码(可能有单元测试,也可能没有单元测试),到那时你会非常感激你把这些断言放在那里。

        2
  •  10
  •   Will    16 年前

    如果单元测试代码是正确的,那么断言就是单元测试发现的bug。

    但是更可能的情况是您的单元测试代码违反了它所测试的单元的约束—您的单元测试代码有bug!

    评论员指出:

    考虑一个单元测试,该函数验证函数正确处理无效输入。

    assert是程序员处理无效输入的一种方式,通过中止程序。通过中止,程序运行正常。

    断言仅在调试生成中(如果 NDEBUG 宏的定义),测试程序在发布版本中是否做了一些合理的事情是很重要的。考虑在发布版本上运行无效的参数单元测试。

    如果您想要两个世界—在调试构建中激发检查断言—那么您希望您的线程内单元测试工具捕获这些断言。你可以通过提供你自己的 断言.h 而不是使用系统1;宏(您希望uu行uuuu和uu文件uuuuu和###expr)将调用您编写的函数,当在单元测试工具中运行时,该函数可能会抛出自定义断言failed。这显然不会捕获编译到链接所针对的其他二进制文件中的断言,但不会使用自定义断言器从源代码进行编译(我建议你这样做,而不是提供你自己的 abort()

        3
  •  4
  •   Vadim Kotov First Zero    8 年前

    当代码被错误地使用时(违反了它的约束或先决条件-在没有初始化的情况下使用库,将NULL传递给不接受它的函数,等等),断言就会被捕获。

    单元测试验证只要代码被正确使用,它就会做正确的事情。

    断言也会在代码进入您认为不可能的状态时捕获(由于您认为不可能,因此无法进行单元测试)。

    Google Test )支架 death tests ,这是验证断言是否正常工作的单元测试(这样您就可以知道断言正在执行捕获无效程序状态的任务)。

        4
  •  3
  •   Péter Török    16 年前

    断言旨在确保某些条件/不变量在程序的生命周期内始终有效。或者更准确地说,为了确保如果这种情况被打破,我们会尽快了解它,尽可能接近问题的根本原因。

    单元测试旨在确保代码的某些部分正常工作 孤立地

    您不能通过单元测试来确保类的环境在实际环境中总是能够完成其契约的一部分。更重要的是,所说的环境包括未来的开发人员,他们可能对控制此类使用的接口契约(无论是隐式的还是仔细记录的)一无所知。以及大量其他软件和硬件组件,这些组件可能随时发生更改和/或损坏,这种方式是特定程序的开发人员无法控制的。

        5
  •  3
  •   R Samuel Klatchko    16 年前

    通常情况下,您不应该触发断言,因为它们应该捕获“不可能的情况”。如果一个断言触发,那应该表明一个bug。

    assert(arg != 0);
    if (arg != 0)
        throw std::runtime_error();
    

    这样,如果一个坏的参数只发生在特定的条件下(即在字段中),它仍然会被捕获。

    如果以这种方式编写代码,则可以关闭断言并编写否定测试,以确保捕获错误参数。

        6
  •  3
  •   Steve Jessop    16 年前

    这里有两种可能性:

    1) 当输入为null时,函数的行为被定义(由它自己的接口显式定义,或由项目的一般规则定义)。因此,单元测试需要测试这种行为。所以你需要一个处理程序来运行一个运行测试用例的进程,处理程序验证代码是否触发了断言并中止了, 你需要嘲笑 assert 不知怎么的。

    2) 当输入为空时,函数的行为没有定义。因此,单元测试不需要传入null—测试也是代码的客户机。如果没有什么特别的东西,你就不能测试它 想象上的 去做。

    没有第三个选项,“当传递null输入时,函数有未定义的行为,但是无论如何测试都传递null,以防发生有趣的事情”。所以我不明白,“在单元测试时,我可以通过禁用断言轻松地绕过这个问题”有什么帮助。当然,单元测试将导致被测函数取消对空指针的引用,这并不比触发断言更好。这些断言之所以存在,是为了阻止更糟糕的事情发生。

    在您的例子中,可能(1)应用于调试构建,而(2)应用于NDEBUG构建。因此,也许您可以只在调试版本上运行空输入测试,并在测试发布版本时跳过它们。

        7
  •  3
  •   Jim Flood    16 年前

    假设一个方法采用一个输入文件的名称,单元测试向它提供一个不存在的文件的名称,以查看是否抛出了一个“filenotfound”异常。这不是“永远不会发生”的事情。它 是

    但是,文件名参数的字符串长度不能为负。如果是的话,那么某处就有一个bug。所以,我可以用assert来表示“这个长度永远不能是负数”(这只是一个人为的例子。)

    对于您的问题,如果函数断言!=NULL,或者单元测试是错误的,不应该发送NULL,因为这永远不会发生,或者,单元测试是有效的,可能发送NULL,函数是错误的,不应该断言!=NULL,必须处理该条件。

        8
  •  1
  •   Community Mohan Dere    9 年前

    就我个人而言,我不倾向于使用断言,因为正如您所发现的,它们通常不能很好地处理单元测试。我倾向于在其他人经常使用断言的情况下抛出异常。这些检查,以及失败时抛出的异常,在调试和发布版本中都存在,我发现它们经常捕捉到即使在发布版本中也“不可能发生”的事情(这些断言通常不会发生,因为它们经常被编译出来)。我发现它更适合我,这意味着我可以编写单元测试,期望在无效输入上抛出异常,而不是期望触发断言。

    很多人不同意,明白吗 1 , 2 etc 但我不在乎。相反,避免断言和使用异常对我很有效,并帮助我为客户机生成健壮的代码。。。

        9
  •  0
  •   Community Mohan Dere    9 年前

    弗斯特 assert (或 ASSERT 或 _ASSERT 或 _ASSERTE

    我想这很容易发生在开发人员的机器上。对于我们的夜间构建,我们只在发布配置中运行单元测试,因此不必担心那里的断言。

    第二 ,一个人可以 normative approach with asserts --

    断言旨在确保某些条件/不变量 在程序的生命周期内始终有效。或是为了更多 尽快了解它,尽可能接近问题的根本原因

    在这种情况下,任何单元测试都不应该引发断言,因为以引发断言的方式调用代码是不可能的。

    我们可以采取“务实”的方法来断言:

    让开发人员在“不要这样做”和“没有实现”的场景中到处散布断言(我们可以整天争论这到底是对是错,但这并不能提供这些特性。)

    如果您采用实用主义的方法,那么单元测试命中断言意味着单元测试以一种非实用主义的方式调用代码 代码支持。这可能意味着代码在发布版本中“什么都不做”,或者意味着代码在发布版本中崩溃,或者意味着代码做了“有趣的事情”。

    • 如果assert附带了一个额外的检查以使调用“无害”,那么进行单元测试 断言测试 (在调试中)和“无害”条件下发布。
        10
  •  0
  •   Sohail Si    10 年前

    1- Assert是在算法开发过程中澄清不变量(循环不变量)的好方法。它对“可读性”和调试都很有用。伯特兰迈耶的语言埃菲尔有一个关键字 不变量 . 在其他语言中, 可用于此目的。

    Assert还可以在其他情况下用作开发过程中的中间解决方案,并在代码完成时逐渐删除。也就是说,作为待办事项。一些 assert 需要用异常处理等来替换(不在上述第#1项中的检查)。如果所有这些检查都显示为断言,则更容易发现它们。

    3- 我有时用它来澄清在某些上下文中(例如在开发新算法时)没有类型检查系统(Python和JavaScript)的语言中的输入类型。不确定这是否是推荐的做法。与第1项一样,它是关于提高程序可读性的。