关于Arrange-Act-Assert的经典测试模式,我经常发现自己在Act之前添加了一个反主张。这样我知道传递的断言实际上是作为动作的结果传递的。
我认为它类似于红绿重构中的红色,只有在测试过程中我看到红色条才知道绿条意味着我编写了代码差异。如果我写了一个通过测试,那么任何代码都会满足它;类似地,关于Arrange-Assert-Act-Assert,如果我的第一个断言失败,我知道任何法案都会通过最终的断言 - 因此它实际上并没有验证该法案的任何内容。
您的测试是否遵循此模式?为什么或为什么不呢?
更新澄清:初始断言基本上与最终断言相反。这不是安排工作的主张;这是法案尚未奏效的断言。
答案 0 :(得分:116)
这不是最常见的事情,但仍然很常见,有自己的名字。此技术称为 Guard Assertion 。您可以在Gerard Meszaros(强烈推荐)的优秀书籍 xUnit Test Patterns 中找到第490页的详细说明。
通常情况下,我自己不使用这种模式,因为我发现编写一个特定的测试更加正确,该测试验证了我认为需要确保的任何前提条件。如果前提条件失败,这样的测试应该总是失败,这意味着我不需要在所有其他测试中嵌入它。这样可以更好地隔离问题,因为一个测试用例只能验证一件事。
对于给定的测试用例,可能需要满足许多前提条件,因此您可能需要多个Guard Assertion。不是在所有测试中重复这些测试,而是对每个前提条件进行一次(和唯一一次)测试,这样可以使测试代码更具可持续性,因为这样可以减少重复次数。
答案 1 :(得分:29)
它也可以指定为Arrange- Assume -Act-Assert。
在NUnit中有一个技术手柄,如下例所示: http://nunit.org/index.php?p=theory&r=2.5.7
答案 2 :(得分:8)
这是一个例子。
public void testEncompass() throws Exception {
Range range = new Range(0, 5);
assertFalse(range.includes(7));
range.encompass(7);
assertTrue(range.includes(7));
}
可能是我写Range.includes()
只是为了返回true。我没有,但我可以想象我可能有。或者我可以用其他许多方式写错了。我希望并期望通过TDD我实际上做对了 - includes()
只是有效 - 但也许我没有。所以第一个断言是一个健全性检查,以确保第二个断言真的有意义。
单独阅读,assertTrue(range.includes(7));
说:“断言修改后的范围包括7”。阅读第一个断言的上下文,它说:“断言调用包含()使它包含7.并且由于包含是我们正在测试的单位,我认为是一些(小) )价值。
我接受了我自己的回答;很多其他人误解了我的问题是关于测试设置。我认为这略有不同。
答案 3 :(得分:7)
Arrange-Assert-Act-Assert
测试总是可以重构为两个测试:
1. Arrange-Assert
和
2. Arrange-Act-Assert
第一次测试只会断言在编配阶段设置的,第二次测试只会断言在法案阶段发生的事情。
这样做的好处是可以提供更准确的反馈,无论是安排还是行动失败,而原来的Arrange-Assert-Act-Assert
这些都是混淆的,你必须深入挖掘并仔细检查什么断言失败以及为什么它失败以便知道是否是失败的安排或行为。
它还可以更好地满足单元测试的目的,因为您将测试分成较小的独立单元。
最后,请记住,每当您在不同的测试中看到类似的“排列”部分时,您应该尝试将它们拉出到共享辅助方法中,以便您的测试更加DRY并且将来可以更加维护。 / p>
答案 4 :(得分:1)
在执行您正在测试的操作之前,在“完整性检查”断言中验证状态是一种旧技术。我通常把它们写成测试脚手架,以证明测试符合我的预期,并在以后删除它们以避免使用测试脚手架进行混乱测试。有时,离开脚手架有助于测试作为叙述。
答案 5 :(得分:1)
我已经读过这种技术了 - 可能是你的btw - 但我不使用它;主要是因为我习惯使用三A形式进行单元测试。
现在,我很好奇,并且有一些问题:你如何编写测试,是否导致这个断言失败,遵循红绿红绿重构循环,或者你之后添加它?
你有时会失败,也许是在你重构代码之后?这告诉你什么?也许你可以分享一个有帮助的例子。谢谢。
答案 6 :(得分:1)
我在调查失败的测试之前已经这样做了。
经过大量的头部刮擦,我确定原因是“安排”期间调用的方法无法正常工作。测试失败是误导性的。我在安排后添加了Assert。这使测试失败的地方突出了实际问题。
如果测试的编配部分太长而且复杂,我认为这里还有代码味道。
答案 7 :(得分:1)
一般来说,我非常喜欢“安排,行动,断言”,并将其作为我的个人标准。然而,它没有提醒我做的一件事是在断言完成时取消我安排的内容。在大多数情况下,这并不会引起太多烦恼,因为大多数事情会通过垃圾收集自动神奇地消失等等。但是,如果您已建立与外部资源的连接,则可能需要在完成后关闭这些连接与你的断言或许多人有一个服务器或昂贵的资源在那里坚持连接或重要资源,它应该能够给别人。如果您在一次或多次测试后one of those developers who does not use TearDown or TestFixtureTearDown进行清理,这一点尤为重要。当然,“安排,行动,断言”不对我未能关闭我打开的内容负责;我只提到这个“陷阱”因为我还没有找到一个好的“A-word”同义词“dispose”推荐!有什么建议吗?
答案 8 :(得分:1)
我现在正在这样做。 A-A-A-A的另一种
Arrange - setup
Act - what is being tested
Assemble - what is optionally needed to perform the assert
Assert - the actual assertions
更新测试示例:
Arrange:
New object as NewObject
Set properties of NewObject
Save the NewObject
Read the object as ReadObject
Act:
Change the ReadObject
Save the ReadObject
Assemble:
Read the object as ReadUpdated
Assert:
Compare ReadUpdated with ReadObject properties
原因是ACT不包含ReadUpdated的读数是因为它不是行为的一部分。这种行为只会改变和储蓄。所以,ARRANGE ReadUpdated for assertion,我正在调用ASSEMBLE进行断言。这是为了防止混淆ARRANGE部分
ASSERT应该只包含断言。这样就在ACT和ASSERT之间留下了ASSEMBLE来设置断言。
最后,如果你在安排中失败,你的测试是不正确的,因为你应该有其他测试来防止/找到这些琐碎的错误。因为对于我现在的场景,应该已经有其他测试READ和CREATE的测试。如果您创建“Guard Assertion”,则可能会破坏DRY并创建维护。
答案 9 :(得分:1)
查看维基百科在Design by Contract上的条目。安排 - 行动 - 断言圣三位一体是试图编码一些相同的概念,并且是关于证明程序的正确性。来自文章:
The notion of a contract extends down to the method/procedure level; the
contract for each method will normally contain the following pieces of
information:
Acceptable and unacceptable input values or types, and their meanings
Return values or types, and their meanings
Error and exception condition values or types that can occur, and their meanings
Side effects
Preconditions
Postconditions
Invariants
(more rarely) Performance guarantees, e.g. for time or space used
在设置此功能所花费的精力与其添加的值之间需要权衡。 A-A-A对于所需的最低步骤是一个有用的提示,但不应阻止任何人创建其他步骤。
答案 10 :(得分:0)
取决于您的测试环境/语言,但通常如果Arrange部分中的某些内容失败,则会抛出异常并且测试无法显示它而不是启动Act部分。所以不,我通常不使用第二个Assert部分。
此外,如果您的编配部分非常复杂并且并不总是抛出异常,您可能会考虑将其包装在某个方法中并为其编写自己的测试,因此您可以确定它不会失败(没有抛出异常)。
答案 11 :(得分:0)
我没有使用那种模式,因为我认为做的事情是:
Arrange
Assert-Not
Act
Assert
可能毫无意义,因为据说你知道你的编配部分正常工作,这意味着必须测试编配部分中的任何内容,或者足够简单,不需要测试。
使用你的答案的例子:
public void testEncompass() throws Exception {
Range range = new Range(0, 5);
assertFalse(range.includes(7)); // <-- Pointless and against DRY if there
// are unit tests for Range(int, int)
range.encompass(7);
assertTrue(range.includes(7));
}
答案 12 :(得分:0)
我用:
1. Setup
2. Act
3. Assert
4. Teardown
因为干净的设置非常重要。