单元测试:为啥预期的参数总是在相等测试中排在第一位?
Posted
技术标签:
【中文标题】单元测试:为啥预期的参数总是在相等测试中排在第一位?【英文标题】:Unit testing: why is the expected argument always first in equality tests?单元测试:为什么预期的参数总是在相等测试中排在第一位? 【发布时间】:2012-02-17 15:45:42 【问题描述】:为什么每个单元测试框架(据我所知)都要求相等测试中的期望值始终是第一个参数:
Assert.AreEqual(42, Util.GetAnswerToLifeTheUniverseAndEverything());
assertEquals(42, Util.GetAnswerToLifeTheUniverseAndEverything());
等等
我现在已经很习惯了,但是我尝试教授单元测试的每个编码员都会犯一个颠倒参数的错误,我完全理解这一点。谷歌没有帮助,也许这里的核心单元测试人员之一知道答案?
【问题讨论】:
您是否查看过测试失败时产生的默认错误消息?这就是为什么。或者你在问为什么这些信息是这样写的?您是在询问担心if( 23 = a )
与if( a = 23 )
的C“标准”吗?你问的是这个吗?
可能只是为了方便,断言表达式可能比较复杂,所以把预期的结果放在第一位很容易找到。
@S.Lott:我知道为什么我需要按顺序通过它们,我想知道为什么大多数单元测试接口都是这样编写的。
您是否在询问担心if( 23 = a )
与if( a = 23 )
的C“标准”以及这在单元测试中如何体现?
@S.Lott:这在某处有记录吗?我同意,这听起来很合理,但我还没有看到任何证据。
【参考方案1】:
似乎大多数早期框架在 actual 之前使用 expected(但出于某种未知原因,可能是掷骰子?)。然而,随着编程语言的发展,以及代码流畅度的提高,这一顺序发生了逆转。大多数流畅的界面通常会尝试模仿自然语言,单元测试框架也不例外。
在断言中,我们要确保某些对象匹配某些条件。这是自然语言形式,就好像你要解释你可能会说的测试代码
“在这个测试中,我确保计算的值等于 5”
而不是
“在这个测试中,我确保 5 等于计算值”。
差异可能不大,但让我们进一步推动它。考虑一下:
Assert.That(Roses, Are(Red));
听起来差不多。现在:
Assert.That(Red, Are(Roses));
嗯..?如果有人告诉你玫瑰是红色的,你可能不会太惊讶。反过来说,red are roses,提出了可疑的问题。 Yoda,有人吗?
尤达提出了一个重要的观点 - 颠倒的顺序迫使你think。
当您的断言更复杂时,它会变得更加不自然:
Assert.That(Forest, Has.MoreThan(15, Trees));
你会如何扭转这一局面? 森林拥有超过 15 棵树?
这种说法(流畅性是修改的驱动因素)以某种方式反映在 NUnit 所经历的变化中 - 最初 (Assert.AreEqual
) 它在 实际 之前使用 预期 >(旧样式)。 Fluent 扩展(或使用 NUnit 的术语,基于约束 - Assert.That
)颠倒了这个顺序。
【讨论】:
不相信硅计算机语言正在与肉类计算机语言融合。y = f(x)
之类的代码不太可能朝着“将 x 传递给 f 并将返回值存储在 y 中”的方向变形。【参考方案2】:
我认为它现在只是一个约定,正如您所说,它被“每个单元测试框架(我知道)”采用。如果您正在使用一个框架,那么切换到另一个使用相反约定的框架会很烦人。所以(例如,如果你正在编写一个新的单元测试框架)你最好也遵循现有的约定。 我相信这来自一些开发人员更喜欢编写他们的相等测试的方式:
if (4 == myVar)
为避免任何不必要的分配,错误地写一个“=”而不是“==”。在这种情况下,编译器会捕捉到这个错误,你会避免很多试图修复奇怪的运行时错误的麻烦。
【讨论】:
【参考方案3】:没有人知道,它是永无止境的混乱的根源。然而,并非所有框架都遵循这种模式(更加混乱):
FEST-Assert 使用正常顺序:
assertThat(Util.GetAnswerToLifeTheUniverseAndEverything()).isEqualTo(42);
Hamcrest:
assertThat(Util.GetAnswerToLifeTheUniverseAndEverything(), equalTo(42))
ScalaTest 并没有真正区分:
Util.GetAnswerToLifeTheUniverseAndEverything() should equal (42)
【讨论】:
【参考方案4】:我不知道,但我参与了一些关于一般相等性测试的参数顺序的热烈讨论。
很多人认为
if (42 == answer)
doSomething();
优于
if (answer == 42)
doSomething();
在基于 C 的语言中。这样做的原因是,如果你不小心放了一个等号:
if (42 = answer)
doSomething();
会给你一个编译器错误,但是
if (answer = 42)
doSomething();
可能不会,并且肯定会引入一个可能难以追踪的错误。那么谁知道呢,也许建立单元测试框架的人习惯于以这种方式考虑相等性测试——或者他们正在复制已经以这种方式建立的其他单元测试框架。
【讨论】:
我认为这是作为理由,当参数将评估顺序与参数顺序匹配时,实际相等性检查的顺序可能更容易观察。【参考方案5】:我认为这是因为 JUnit 是大多数单元测试框架的先驱(不是说它是第一个单元测试框架,而是它引发了单元测试的爆炸式增长)。由于 JUnit 是这样做的,所有后续的框架都复制了这种形式,并成为了一种约定。
为什么 JUnit 会那样做?我不知道,问肯特贝克!
【讨论】:
【参考方案6】:我对此的看法是避免任何例外,例如:42.equals(null) vs null.equals(42)
预期为 42 null 是实际的
【讨论】:
可能有算法在给定42时输出null,或者在给定null时输出42。【参考方案7】:好吧,他们必须选择一个约定。如果你想扭转它,试试 Hamcrest 匹配器。它们旨在帮助提高可读性。这是一个基本示例:
import org.junit.Test;
import static org.junit.Assert.assertThat;
import static org.hamcrest.core.Is.is;
public HamcrestTest
@Test
public void matcherShouldWork()
assertThat( Math.pow( 2, 3 ), is( 8 ) );
【讨论】:
我不知道你在 py.test 中所做的那样比普通的assert 8 == Math.pow(2, 3)
更具可读性!
1) 你不能混淆期望值与实际值的顺序 2) 你可以扩展匹配器,这样你就可以向 Hamcrest 添加更多“动词”,就像你可以添加一样assertThat( Math.pow( 2, 3 ), is(relativelyPrimeTo( 49 )) );
【参考方案8】:
当然,将期望值放在首位是合乎逻辑的,因为它是第一个已知值。
在手动测试的背景下考虑它。手动测试将写入预期值,然后记录实际值。
【讨论】:
“逻辑意义”?短语“我期待‘生命的意义是什么?’这个问题的答案。 to equal 42" 按逻辑组织,合乎逻辑。 是的,句子排列正确。我更多地谈论期望的概念。期望首先出现在逻辑上。看看谷歌对期望的定义:“强烈相信某事会发生或会发生”。 将发生。即它会发生在 预期设定之后。 不,它没有。以上是关于单元测试:为啥预期的参数总是在相等测试中排在第一位?的主要内容,如果未能解决你的问题,请参考以下文章