为什么 assertEquals()参数的顺序是(预期的、实际的) ?

为什么这么多的 assertEquals()或类似的函数把期望值作为第一个参数,实际值作为第二个参数?
这对我来说似乎有悖常理,那么这个不寻常的顺序有什么特别的原因吗?

46279 次浏览

因为作者有50% 的几率能跟你的直觉匹配。

因为另一个过载

assertWhatever(explanation, expected, actual)

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

The xUnit testing convention is expected/actual. So, for many that is the natural order since that's what they learnt.

Interestingly in a break from convention for an xUnit framework qunit goes for actual/expected. At least with javascript you can just create a new function that encapsulates the old one and assign it the original variable:

var qunitEquals = equals;
equals = function(expected, actual, message) {
qunitEquals(actual, expected, message);
};

I agree with the consensus that consistency is #1, but the behavior of comparing dictionaries may be a helpful datapoint if you're evaluating this question.

当我在 diff 上看到一个“ +”时,我将其解读为“正在测试的过程添加了这个”同样,个人喜好也适用。

Note: I used alphabetized keys and made the dictionary longer so that only a middle key would change for clarity of the example. Other scenarios display more obfuscated diffs. Also noteworthy, Assertequals 使用 assertDictequals in >=2.7 and >=3.1

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',
}
)

产出:

$ 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)

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

一个简单的函数调用显示了 左边的返回值右边呼叫

    y = f(x)

根据这个约定,函数的自测试演示可能类似于:

    assertEqual(y, f(x))

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

下面是 sum ()函数的演示,左边是一个字面 意料之中返回值,右边是一个计算 真的返回值的函数调用:

    assertEqual(15, sum((1,2,3,4,5)))

类似地,下面是一个表达式的演示,它也是按照(预期的、实际的)顺序自然排列的:

    assertEqual(4, 2 + 2)

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

assertEqual(42, 2 * 3 * 7)
assertEqual(42, (1 << 1) + (1 << 3) + (1 << 5))
assertEqual(42, int('110', int('110', 2)))

我怀疑这解开了肯特 · 贝克所说的“预期优先使他们阅读更好”的神秘 @ChrisPovirk raised

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

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

  1. 直觉/反直觉可以被认为是主观的,所以不管最初定义的顺序是什么,也许是 50% 的人都不会高兴

  2. 就我个人而言,我更希望它被设计为 assertEqual(actual, expected),因为,考虑到 assertif之间的概念相似性,我希望它遵循 ABC3的标准,例如 if a == 1

    (附注: 的确有不同的意见,其中 提示按“反向顺序”写入 if 语句,即 if(1==a) {...},为了防止你意外地错过了一个 =。但即使在 C/C + + 世界中,这种风格也远非常规。而且,如果您正好在编写 Python 代码,那么首先就不容易出现这种令人讨厌的输入错误,因为 if a = 1在 Python 中是无效的。)

  3. The practical convincing reason to do assertEqual(expect, actual), is that the unittest library in your language likely already follows that order to generate readable error message. For example, in Python, when you do assertEqual(expected_dictionary, actual_dictionary), unittest will display missing keys in actual with prefix ABC2, and extra keys with prefix +, just like when you do a git diff old_branch new_branch.

    Intuitive or not, this is the single most convincing reason to stick with the assertEqual(expected, actual) order. If you happen to not like it, you better still accept it, because "practicality beats purity".

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

给你,祝你 assertEqual(expect, actual)快乐!

来自 肯特 · 贝克answer,JUnit 的共同创建者(其中可能有这个约定 起源,因为他早期的 SUnit 似乎没有包括 assertEquals) :

将一堆 assertEquals 排成一行。先预期会让它们读起来更好。

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

assertEquals(12345, user.getId());
assertEquals("kent", user.getUsername());
assertEquals("Kent Beck", user.getName());

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

assertEquals(user.getId(), 12345);
assertEquals(user.getUsername(), "kent");
assertEquals(user.getName(), "Kent Beck");

(我倾向于这个顺序还有其他原因,但是对于这个关于 为什么的问题,它是另一种方式,肯特的推理似乎是答案。)

然而,Bob Stein 的 下面是一条评论(非常类似于 这个)提出了一些“预期优先”的建议。其主要思想是期望值可能通常更短——通常是文字或变量/字段,而不是可能更复杂的方法调用。结果是:

assertEquals(12345,       user.getId());
assertEquals("kent",      user.getUsername());
assertEquals("Kent Beck", user.getName());

谢谢,鲍勃!

我听到的解释是它来自 TDD。

在测试驱动开发中,您从测试开始,然后编写代码。

通过编写期望开始断言,然后调用应该产生它的代码,是这种思维模式的迷你版本。

当然,这可能只是一个人们讲的故事,不知道这是一个有意识的原因。

assertEqual的文档命名为第一个参数 first和第二个参数 second:

assertEqual(first, second, msg=None)

测试 firstsecond是否相等。如果值不相等,测试将失败。

然而,如果您查看文档中的大多数示例,它们将接收值放在第一位,预期值放在第二位(与您的问题后面声称的相反) :

self.assertEqual(self.widget.size(), (50,50), 'incorrect default size')

所以我会说大会是 assertEqual(got, expected),而不是反过来!

Either way, your tests will still work.

I'm a little surprised not to see this answer already, because it's always seemed like the most likely explanation to me.

Imagine you 没有 assertEquals, but just assert. How would you write the test? You might think to write it as:

assert(actual == expected)

但是在许多情况下,它们不是同一个对象,只是等价的对象,所以(这可能与语言有关) ,您不能可靠地使用 ==操作符来表达您的意图。所以你把它切换到:

assert(actual.equals(expected))

一切都会好起来的。但是,当您引入一个 bug 时,测试失败了,因为结果(实际)变成了 null。但是这个测试并没有像您预期的那样失败——相反,您甚至根本不能调用 actual.equals,因为您甚至没有一个对象来调用方法!您的测试代码因为一个异常而崩溃,因为测试本身是脆弱的。

但是您的 意料之中对象将 永远不会为空。

许多使用面向对象语言的人已经习惯了这一点,他们习惯于编写所有基于方法的条件语句,比如 if ("foo".equals(myString)),在 myString为 null 的情况下这仍然是安全的(尽管反过来也不安全)。

因此,用于写断言的 最好的习惯是:

assert(expected.equals(actual))

... which fails if actual is wrong, even null.

一旦您在这种情况下花费了几年时间,并决定使用 assertEquals方法编写一个单元测试框架,那么对您来说只有一种自然的参数顺序:)