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

7ivaypg9  于 2022-11-14  发布在  其他
关注(0)|答案(9)|浏览(152)

为什么这么多assertEquals()或类似的函数把期望值作为第一个参数,把实际值作为第二个参数?
我觉得这似乎违反了直觉,那么这种不寻常的顺序有什么特殊的原因吗?

wrrgggsh

wrrgggsh1#

answer来自Kent Beck,JUnit的共同创建者(可能是这个约定的起源,因为他早期的SUnit doesn't appear to have included assertEquals):
把一堆Assert等号排成一行。先有预期会让他们读得更好。
在我最初的答案中,我说我不明白这一点。下面是我在测试中经常看到的:

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

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

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

(And还有其他原因使我更喜欢这个顺序,但对于这个问题的目的,即“为什么"是另一种方式,肯特的推理似乎是答案。)
然而,Bob Stein在下面有一条评论(很像这条),它建议了“expected first”的几个优点。主要的想法是,期望值通常较短--通常是文字或变量/字段,而不是复杂的方法调用。结果是:

  • 更容易识别预期值和实际值。
  • 可以使用少量额外的空格来对齐它们(如果您喜欢这种方式,尽管我没有看到在我能轻易找到的最早的JUnit提交中使用过):
assertEquals(12345,       user.getId());
assertEquals("kent",      user.getUsername());
assertEquals("Kent Beck", user.getName());

谢谢你,鲍勃!

4c8rllxm

4c8rllxm2#

因为作者有50%的几率符合你的直觉。
由于其他过载

assertWhatever(explanation, expected, actual)

而解释,也就是你所知道的一部分,与预期的,也就是你所知道的,相对于实际的,你在写代码的时候并不知道。

igsr9ssn

igsr9ssn3#

assertEqual()的另一个目的是为人类读者演示代码。
一个简单的函数调用在左侧显示返回值,在右侧显示调用。

y = f(x)

按照该约定,函数的自测演示如下所示:

assertEqual(y, f(x))

顺序为(预期,实际)。
下面是sum()函数的一个演示,左边是一个字面的expected返回值,右边是一个计算actual返回值的函数调用:

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

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

assertEqual(4, 2 + 2)

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

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

我怀疑这解决了@ChrisPovirk提出的关于肯特Beck所说的“期望第一使他们读得更好”的谜团。
感谢Andrew WeimholtGanesh Parameswaran提供这些公式。

5uzkadbs

5uzkadbs4#

我同意一致性是第一位的共识,但是如果你在评估这个问题,比较字典的行为可能是一个有用的数据点。
当我在diff上看到一个“+”时,我会把它读成“被测试的过程添加了这个”。
注意:我使用了按字母顺序排列的键,并增加了字典的长度,这样只有中间的一个键会发生变化,以使示例更加清晰。其他场景显示了更多模糊的差异。同样值得注意的是,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',
            }
        )

输出量:

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

nwnhqdif5#

这是一个非常有趣的主题,这里也有很多非常有教育意义的答案!下面是我从他们那里学到的:
1.直觉/反直觉可以被认为是主观的,所以不管它最初被定义的顺序是什么,也许是50% of us would not be happy
1.就我个人而言,我更希望它被设计为assertEqual(actual, expected),因为考虑到assertif之间的概念相似性,我希望它遵循if actual == expect的规范,例如if a == 1
(PS:确实,有不同的意见提示以“相反的顺序”编写if语句,即if(1==a) {...},以防止意外地丢失一个=。但这种风格远远不是规范,即使在C/C++世界中也是如此。如果你碰巧在编写Python代码,你一开始就不会受到这种讨厌的错别字的影响,因为if a = 1在Python中无效。)
1.执行assertEqual(expect, actual)的实际理由是,你的语言中的unittest库很可能已经按照这个顺序生成了可读的错误消息。例如,在Python中,当你执行assertEqual(expected_dictionary, actual_dictionary)时,unittest will display missing keys in actual with prefix - , and extra keys with prefix +,就像你执行git diff old_branch new_branch一样。
不管直观与否,这是坚持assertEqual(expected, actual)顺序的唯一最有说服力的理由。如果你碰巧不喜欢它,你最好还是接受它,因为"practicality beats purity"
1.最后,如果你需要一种方法来帮助你记住顺序,this answerassertEqual(expected_result, actual_calculation)与赋值语句的顺序result = calculate(...)进行比较。这是一种记忆事实行为的好方法,但恕我直言,这不是一种更直观的顺序推理。
所以给你。快乐assertEqual(expect, actual)

ibps3vxo

ibps3vxo6#

xUnit测试惯例是预期的/实际的。所以,对于许多人来说,这是自然的顺序,因为这是他们学到的。
有趣的是,与xUnit框架的惯例不同,qunit使用的是actual/expected。至少在javascript中,你可以创建一个新的函数来封装旧的函数,并将原来的变量赋给它:

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

h5qlskok7#

assertEqual的文档将第一个参数命名为first,将第二个参数命名为second
assertEqual(first, second, msg=None)
测试firstsecond是否相等。如果两个值不相等,则测试失败。
然而,如果你看一下文档中的大多数例子,你会发现他们把收到的价值放在第一位,而期望值放在第二位(与你的问题贴所宣称的相反):

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

所以我会说约定是assertEqual(got, expected),而不是相反!
不管怎样,你的测试仍然有效。

swvgeqrz

swvgeqrz8#

我有点惊讶还没有看到这个答案,因为它一直似乎是最有可能的解释给我。
假设你没有assertEquals,而只有assert,你会怎么写测试呢?你可以把它写为:

assert(actual == expected)

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

assert(actual.equals(expected))

在一段时间内一切都很好。但是随后你引入了一个bug,测试失败了,因为结果(实际)变成了***null***。但是测试并没有像你期望的那样失败--相反,你甚至根本不能调用actual.equals,因为你甚至没有一个对象来调用方法!你的测试代码因为一个异常而崩溃,因为测试本身是脆弱的。

  • 但是您的预期对象永远不会为空。*

许多使用OO语言的人已经习惯了这一点,他们养成了编写所有基于方法的条件语句的习惯,比如if ("foo".equals(myString)),在myString为空的情况下,这仍然是安全的(尽管相反的情况就不安全了)。
因此,写Assert的 * 最佳习惯 * 是:

assert(expected.equals(actual))

...如果实际值错误,甚至为空,则会失败。
一旦你在这种情况下花了几年时间,并且你决定用assertEquals方法编写一个单元测试框架,那么你只会感觉到一种自然的参数顺序:)

6xfqseft

6xfqseft9#

我听到的解释是它来自TDD。
在测试驱动开发中,您从测试开始,然后编写代码。
通过编写期望值来启动Assert,然后调用应该生成它的代码,是这种心态的一个迷你版本。
当然,这可能只是人们讲的一个故事,不知道是不是有意识的原因。

相关问题