为什么assertEquals()参数在顺序中(预期的,实际的)?

jor*_*jor 52 debugging assert

为什么这么多assertEquals()或类似的函数将期望值作为第一个参数而实际的函数作为第二个参数?
这对我来说似乎是违反直觉的,所以这种不寻常的订单有什么特别的原因吗?

bma*_*ies 26

因为作者有50%的机会匹配你的直觉.

因为其他超负荷

assertWhatever(explanation, expected, actual)
Run Code Online (Sandbox Code Playgroud)

而这个解释是你所知道的一部分,与预期的一致,就是你所知道的,而不是你在编写代码时不知道的实际情况.

  • 但是在这种情况下,它会略微不一致,因为在大多数语言中,首先出现所需的参数,最后出现强制性参数.这就是为什么我会更自然地去断言什么(实际的,预期的[,解释]) (13认同)
  • 没错,jor!此外,在条件句中更常见的是写if(s =="a")而不是if("a"== s)(虽然可辩论是否会更好反过来 - 在共性方面第一个虽然赢了一场). (3认同)
  • 我认为这对于50%的人口来说是不直观的……您不会说`assertGreater(a,b)`断言`b> a`是直观的...(而且我认为这就是为什么他们删除了非可交换方法) (2认同)
  • 如果您不小心执行了赋值“ =”而不是等效检查`==`,则首先放置常量是一种获取错误的方法,因为当您尝试重新赋值常量时会抛出错误。(我想我首先看到它是在“编写可靠的代码”中得到推广的)。它创建(IMHO)可读性较低的代码(我更喜欢“结果应符合预期”样式语法),但是如果忽略了=,则会使编译器破产标志 (2认同)

Chr*_*irk 26

回答来自肯特·贝克,JUnit的共同创造者(其中可能这个惯例起源,因为他早先苏尼特不会出现已包括assertEquals):

将一堆 assertEquals 排成一行。先有预期会让他们读得更好。

在我的回答的初始版本中,我说我不明白这一点。这是我在测试中经常看到的:

assertEquals(12345, user.getId());
assertEquals("kent", user.getUsername());
assertEquals("Kent Beck", user.getName());
Run Code Online (Sandbox Code Playgroud)

我认为首先使用实际值会更好地阅读。这将更多重复的样板放在一起,对齐我们正在测试其值的方法调用:

assertEquals(user.getId(), 12345);
assertEquals(user.getUsername(), "kent");
assertEquals(user.getName(), "Kent Beck");
Run Code Online (Sandbox Code Playgroud)

(我更喜欢这个顺序还有其他原因,但就这个为什么是另一种方式的问题而言,肯特的推理似乎是答案。)

然而,鲍勃·斯坦( Bob Stein)在下面发表了一条评论(很像这个评论),它暗示了一些“预期第一”已经实现的事情。主要思想是期望值通常可能更短——通常是文字或变量/字段,而不是可能的复杂方法调用。其结果:

assertEquals(12345,       user.getId());
assertEquals("kent",      user.getUsername());
assertEquals("Kent Beck", user.getName());
Run Code Online (Sandbox Code Playgroud)

谢谢,鲍勃!

  • 我认为理解肯特·贝克观点的关键是期望值往往更短。因此,在逗号之后需要更少的空格来排列参数——假设有人愿意反抗 [PEP8 对无关空格的抗议](https://www.python.org/dev/peps/pep-0008/#whitespace -in-表达式和语句)当然。 (2认同)

Bob*_*ein 9

的另一个目的assertEqual()是为人类读者演示代码。

一个简单的函数调用显示左侧返回值右侧调用

    y = f(x)
Run Code Online (Sandbox Code Playgroud)

按照该约定,该功能的自测试演示可能如下所示:

    assertEqual(y, f(x))
Run Code Online (Sandbox Code Playgroud)

订单是(预期的,实际的)。

这是 sum() 函数的演示,左侧是文字预期返回值,右侧是计算实际返回值的函数调用:

    assertEqual(15, sum((1,2,3,4,5)))
Run Code Online (Sandbox Code Playgroud)

同样,这里有一个表达式的演示。按(预期的,实际的)顺序也是很自然的:

    assertEqual(4, 2 + 2)
Run Code Online (Sandbox Code Playgroud)

另一个原因是风格。如果你喜欢排列,左边的预期参数更好,因为它往往更短:

assertEqual(42, 2 * 3 * 7)
assertEqual(42, (1 << 1) + (1 << 3) + (1 << 5))
assertEqual(42, int('110', int('110', 2)))
Run Code Online (Sandbox Code Playgroud)

我怀疑这解决了@ChrisPovirk 提出的关于 Kent Beck 所说的“预期首先让他们读得更好”的谜团。

感谢Andrew WeimholtGanesh Parameswaran提供这些公式。

  • 同意。我什至会说它比“别有用心”更强烈,可能是“主要动机”。可以说,代码几乎主要是供其他开发人员(和您自己)阅读的。恕我直言,可读性和可理解性是首要关注的问题。 (2认同)

Ray*_*Luo 7

这是一个非常有趣的话题,这里也有很多非常有教育意义的答案!这是我从他们那里学到的:

  1. Intuitive/counter-intuitive 可以被认为是主观的,所以不管它最初定义的顺序是什么,也许我们 50% 的人都不会高兴

  2. 就我个人而言,我更喜欢将其设计为assertEqual(actual, expected),因为鉴于assert和之间的概念相似性if,我希望它遵循的规范if actual == expect,例如,if a == 1

    (PS:确实有不同的意见,提示写if语句“逆序”,即if(1==a) {...}为了防止你不小心漏了一个=。但这种风格远非常态,即使在C/ C++ 世界。如果您碰巧正在编写 Python 代码,那么首先您就不会受到那个讨厌的错别字的影响,因为if a = 1在 Python 中无效。)

  3. 这样做的实际令人信服的理由assertEqual(expect, actual)是,您语言中的 unittest 库可能已经按照该顺序生成可读的错误消息。例如,在Python中,当你这样做assertEqual(expected_dictionary, actual_dictionary)单元测试将显示实际丢失的钥匙前缀-和额外的键前缀+,只是当你做一个像git diff old_branch new_branch

    无论直观与否,这是坚持assertEqual(expected, actual)顺序的唯一最有说服力的理由。如果你碰巧不喜欢它,你最好还是接受它,因为“实用胜过纯洁”

  4. 最后,如果您需要一种方法来帮助您记住顺序,则此答案assertEqual(expected_result, actual_calculation)可与赋值语句 order进行比较result = calculate(...)。这可能是记住事实上的行为的好方法,但恕我直言,这不是该顺序无可争议的推理更直观。

所以你来了。快乐assertEqual(expect, actual)

  • 对于第 4 点,很有趣的是,分配的参数顺序实际上在计算的早期阶段引起了很多争议。一些 CS 主义者认为,使用顺序可以将其读作“值存储在值中”,这会减少与数学等式的混淆(当然,使用与“=”不同的东西来进行赋值,例如某种箭头,例如'-&gt;')。 (2认同)

whp*_*whp 6

我同意一致性是#1的共识,但如果你正在评估这个问题,比较字典的行为可能是一个有用的数据点.

当我在差异上看到"+"时,我将其读作"正在测试的程序添加了这个".同样,个人偏好适用.

注意:我使用按字母顺序排列的键并使字典更长,以便只有中间键会因示例的清晰度而改变.其他场景显示更多混淆的差异.另外值得注意的是,assertEqual在> = 2.7和> = 3.1中使用assertDictEqual

exl.py

from unittest import TestCase


class DictionaryTest(TestCase):

    def test_assert_order(self):
        self.assertEqual(
            {
                'a_first_key': 'value',
                'key_number_2': 'value',
                'z_last_key': 'value',
                'first_not_second': 'value',
            },
            {
                'a_first_key': 'value',
                'key_number_2': 'value',
                'z_last_key': 'value',
                'second_not_first': 'value',
            }
        )
Run Code Online (Sandbox Code Playgroud)

输出:

$ python -m unittest exl
F
======================================================================
FAIL: test_assert_order (exl.DictionaryTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "exl.py", line 18, in test_assert_order
    'second_not_first': 'value',
AssertionError: {'a_first_key': 'value', 'z_last_key': 'value', 'key_number_2': 'value', 'first_ [truncated]... != {'a_first_key': 'value', 'z_last_key': 'value', 'key_number_2': 'value', 'second [truncated]...
  {'a_first_key': 'value',
-  'first_not_second': 'value',
   'key_number_2': 'value',
+  'second_not_first': 'value',
   'z_last_key': 'value'}

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
Run Code Online (Sandbox Code Playgroud)


Ed *_*kes 5

xUnit测试约定是预期/实际的。所以,对于许多人来说,这是自然的顺序,因为这就是他们学到的。

有趣的是,与 xUnit 框架的惯例不同,qunit 代表实际/预期。至少使用 JavaScript,您可以创建一个封装旧函数的新函数并将其分配给原始变量:

var qunitEquals = equals;
equals = function(expected, actual, message) {
    qunitEquals(actual, expected, message);
};
Run Code Online (Sandbox Code Playgroud)