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

发布于 2024-08-23 22:28:43 字数 224 浏览 3 评论 0原文

Why do so many assertEquals() or similar functions take the expected value as the first parameter and the actual one as second?
This seems counterintuitive to me, so is there a particular reason for this unusual order?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

各自安好 2024-08-30 22:28:43

答案来自Kent Beck,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");

我更喜欢这个顺序还有其他原因,但出于这个关于为什么的问题的目的,它是换句话说,肯特的推理似乎就是答案。)

但是,鲍勃斯坦有下面的评论(很像这个)表明了一些“首先期望”的事情。主要思想是期望值通常可能更短——通常是文字或变量/字段,而不是可能复杂的方法调用。因此:

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

谢谢,鲍勃!

The answer from Kent Beck, co-creator of JUnit (where possibly this convention originates, since his earlier SUnit doesn't appear to have included assertEquals):

Line a bunch of assertEquals in a row. Having expected first makes them read better.

In the initial version of my answer, I said that I didn't understand this. Here's what I often see in tests:

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

I would think this would read better with the actual value first. That puts more of the repetitive boilerplate together, aligning the method calls whose values we're testing:

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

(And there are other reasons that I prefer this order, but for the purpose of this question about why it's the other way, Kent's reasoning appears to be the answer.)

However, Bob Stein has a comment below (much like this one) that suggests a couple things that "expected first" has going for it. The main idea is that expected values are probably typically shorter -- often literals or variables/fields, rather than possibly complex method calls. As a result:

  • It's easier to identify both the expected and actual values at a glance.
  • It's possible to use a small amount of extra whitespace to align them (if you prefer that kind of thing, though I don't see it used in the earliest JUnit commit I could find easily):
assertEquals(12345,       user.getId());
assertEquals("kent",      user.getUsername());
assertEquals("Kent Beck", user.getName());

Thanks, Bob!

笑,眼淚并存 2024-08-30 22:28:43

因为作者有 50% 的机会符合你的直觉。

由于其他重载

assertWhatever(explanation, expected, actual)

,并且解释是您所知道的一部分,与预期一致,即您所知道的,而不是实际的,您在编写代码时不知道。

Because the authors had a 50% chance of matching your intuition.

Because of the other overload

assertWhatever(explanation, expected, actual)

And the explanation, which is part of what you know, goes with the expected, which is what you know, as opposed to the actual, which you don't know at the time you write the code.

慕巷 2024-08-30 22:28:43

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提出关于肯特·贝克所说的“首先预期让他们读得更好”的含义。

谢谢Andrew WeimholtGanesh Parameswaran 这些公式。

An ulterior purpose of assertEqual() is to demo code for human readers.

A simple function call shows the return value on the left and the call on the right.

    y = f(x)

Following that convention, a self-testing demonstration of the function could look like:

    assertEqual(y, f(x))

The order is (expected, actual).

Here's a demo of the sum() function with a literal expected return value on the left, and a function call that calculates the actual return value on the right:

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

Similarly, here's a demo of an expression. It is also natural in (expected, actual) order:

    assertEqual(4, 2 + 2)

Another reason is stylistic. If you like lining things up, the expected parameter is better on the left because it tends to be shorter:

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

I suspect this solves the mystery @ChrisPovirk raised about what Kent Beck meant by "expected first makes them read better."

Thanks Andrew Weimholt and Ganesh Parameswaran for these formulae.

不如归去 2024-08-30 22:28:43

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

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

  2. 就我个人而言,我更希望将其设计为 assertEqual(actual, Expected),因为考虑到 assertif 之间概念上的相似性,我希望它遵循 的规范if实际==期望,例如,if a == 1

    (PS:确实存在不同意见提示以“相反的顺序”编写if语句,即if(1==a) {...},以防止您不小心漏写一个<但这种风格远非常态,即使在 C/C++ 世界中,如果您碰巧正在编写 Python 代码,那么您一开始就不会受到这种令人讨厌的拼写错误的影响。 code>if a = 1 在 Python 中无效。)

  3. 执行 assertEqual(expect,actual) 的实际令人信服的原因是,您语言中的单元测试库可能已经遵循该顺序生成可读的错误消息。例如,在 Python 中,当您执行 assertEqual(expected_dictionary,actual_dictionary) 时,unittest 将显示实际中缺少的键带有前缀 - 和带有前缀 + 的额外键,就像执行 git diff old_branch new_branch 时一样。

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

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

那么就到这里吧。快乐 assertEqual(expect,actual)

This is a very intesting topic, and lots of very educational answers here too! Here is what I learn from them:

  1. Intuitive/counter-intuitive can be considered as subjective, so no matter which order it was originally defined, perhaps 50% of us would not be happy.

  2. Personally I would have preferred it were designed as assertEqual(actual, expected), because, given the conceptual similarity between assert and if, I would wish it follows the norm of if actual == expect, for example, if a == 1.

    (PS: It is true that there are different opinions which prompts to write if statement in the "reverse order", i.e. if(1==a) {...}, in order to guard you from accidentally missing one =. But that style was far from the norm, even in the C/C++ world. And if you happen to be writing Python code, you are not vulnerable to that nasty typo in the first place, because if a = 1 is not valid in 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 -, 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. Lastly, if you need a way to help you remember the order, this answer compares assertEqual(expected_result, actual_calculation) to the assignment statement order result = calculate(...). It can be a good way to MEMORIZE the de-facto behavior, but IMHO it is not the undebatable reasoning of that order is more intuitive.

So here you go. Happy assertEqual(expect, actual) !

2024-08-30 22:28:43

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

当我在差异上看到“+”时,我将其读作“正在测试的程序添加了此内容”。再次强调,个人喜好适用。

注意:为了使示例清晰,我使用了按字母顺序排列的键并使字典更长,以便只有中间的键会发生变化。其他场景显示更多模糊的差异。另外值得注意的是,assertEqual在>=中使用assertDictEqual 2.7 和 >=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)

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

When I see a "+" on a diff, I read this as "the procedure being tested added this." Again, personal preferences apply.

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, assertEqual uses assertDictEqual in >=2.7 and >=3.1

File 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:

python -m unittest exl

Output:

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)
羁拥 2024-08-30 22:28:43

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

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

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

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);
};
内心荒芜 2024-08-30 22:28:43

assertEqual 将第一个参数命名为 first,将第二个参数命名为 second

assertEqual(第一、第二、msg=None)

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

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

self.assertEqual(self.widget.size(), (50,50), '默认大小不正确')

所以我想说约定是 assertEqual(got, Expected),而不是相反!

不管怎样,你的测试仍然有效。

The documentation for assertEqual names the first parameter first, and the second parameter second:

assertEqual(first, second, msg=None)

Test that first and second are equal. If the values do not compare equal, the test will fail.

However, if you look at most of the examples in the documentation, they place the received value first, and the expected value second (the opposite of what your question post claims):

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

So I would say the convention is assertEqual(got, expected), and not the other way round!

Either way, your tests will still work.

眸中客 2024-08-30 22:28:43

我有点惊讶没有看到这个答案,因为对我来说这似乎总是最有可能的解释。

想象一下,您没有 assertEquals,而只有 assert。你会如何写测试?您可能会认为将其写为:

assert(actual == expected)

但在许多情况下,它们不会是同一个对象,只是等效的对象,因此(这可能与语言相关),您无法可靠地使用 ==< /code> 运算符来表达您的意图。所以你将其切换为:

assert(actual.equals(expected))

暂时一切都很好。但随后你引入了一个错误,测试失败了,因为结果(实际)变成了null。但是测试并没有按照您期望的方式失败——相反,您甚至根本无法调用actual.equals,因为您甚至没有可以调用方法的对象!您的测试代码会因异常而崩溃,因为测试本身很脆弱。

但是你的预期对象将永远为空。

许多使用面向对象语言工作的人已经习惯了这一点,并且他们养成了编写所有内容的习惯基于方法的条件语句,例如 if ("foo".equals(myString)),在 myString 为 null 的情况下仍然是安全的(尽管反之则不安全) 。

因此,编写断言的最佳习惯是:

assert(expected.equals(actual))

...如果实际错误,甚至为空,则失败。

一旦您在这种情况下度过了几年,并且决定使用 assertEquals 方法编写一个单元测试框架,那么只有一种参数排序对您来说会感觉很自然: )

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 didn't have assertEquals, but just assert. How would you write the test? You might think to write it as:

assert(actual == expected)

But in many cases, they won't be the same object, just equivalent ones, so (and this is perhaps language-dependent), you can't reliably use the == operator to express your intent. So you switch it to:

assert(actual.equals(expected))

And things are fine for a while. But then you introduce a bug, and the test fails, because the result (actual) becomes null. But the test doesn't fail the way you expect -- instead, you can't even invoke actual.equals at all, because you don't even have an object to call a method on! Your test code blows up with an exception because the test itself is fragile.

But your expected object will never be null.

Many people working in OO languages have got used to this, and they make a habit of writing all method-based conditionals like if ("foo".equals(myString)), which is still safe in the case that myString is null (though the reverse is not safe).

So the best habit for writing asserts is:

assert(expected.equals(actual))

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

Once you've spent some years in this kind of situation, and you decide to write a unit testing framework with an assertEquals method, there's only one ordering of the arguments that is going to feel natural to you :)

烟雨扶苏 2024-08-30 22:28:43

当期望值是较长的文本时,测试代码在实际期望值之后更具可读性并且更易于维护:

self.assertEqual(obj.body, """Some longer 
text with multiple lines.
""")

When the expected value is a longer text, the test code is more readable and easier to maintain when it comes after the actually expected value:

self.assertEqual(obj.body, """Some longer 
text with multiple lines.
""")
笑忘罢 2024-08-30 22:28:43

我听到的解释是它来自测试驱动开发 (TDD)。

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

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

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

The explanation I heard is that it comes from test-driven development (TDD).

In test-driven development, you start with the test, and then write the code.

Starting assertions by writing the expectation, and then call the code that should produce it, is a mini version of that mindset.

Of course, this may just be a story people tell. Don't know that it was a conscious reason.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文