应该是“安排 – 断言 – 行为 – 断言”?

关于Arrange-Act-Assert的典型testing模式,我经常发现自己在Act之前加了一个反指。 通过这种方式,我知道传递的断言确实是作为行动的结果而传递的。

我认为这与红绿重构中的红相似,只有当我在testing过程中看到红条时,我才知道绿条意味着我编写了代码才能起作用。 如果我写一个合格的testing,那么任何代码都能满足它; 同样,关于安排 – 断言 – 行为 – 断言,如果我的第一个断言失败了,我知道任何法案都会通过最后的断言 – 所以它实际上并没有对该法案进行任何validation。

你的testing是否遵循这种模式? 为什么或者为什么不?

更新说明 :最初的断言本质上与最终断言相反。 安排工作并不是一个断言。 这是一个断言,行为尚未奏效。

这不是最常见的事情,但仍然有足够的自己的名字。 这种技术被称为Guard Assertion 。 您可以在Gerard Meszaros出色的xUnit Test Patterns (强烈推荐)第479页中find它的详细说明。

通常情况下,我自己并不使用这种模式,因为我发现写一个特定的testing来validation我认为需要确保的任何先决条件是更正确的。 如果前提条件失败,这样的testing应该总是失败,这意味着我不需要将其embedded所有其他testing中。 这样可以更好地隔离问题,因为一个testing用例只能validation一件事情。

对于给定的testing用例,可能有许多前提条件需要满足,所以您可能需要多个Guard Assertion。 与其在所有testing中重复这些testing,对于每个前提条件都进行一次(而且只有一次)testing,这样可以使testing代码更加可持续,因为这样可以减less重复次数。

它也可以被指定为Arrange- Assume -Act-Assert。

在NUnit中有这样的技术手段,如下例所示: http : //nunit.org/index.php? p=theory&r= 2.5.7

这是一个例子。

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”。 阅读第一个断言的上下文,它是这样说的:“断言调用encompass()使它包含7.因为encompass是我们正在testing的单元,我认为这是一些(小)值。

我接受我自己的答案; 很多其他人误解我的问题是关于testing设置。 我觉得这有点不同。

Arrange-Assert-Act-Asserttesting总是可以重构为两个testing:

 1. Arrange-Assert 2. Arrange-Act-Assert 

这样做的好处是可以更准确地反馈安排或失败的行为,而无需在原始testing中运行debugging器。 它也更好地满足了unit testing的意图,因为您正在将testing分成更小的独立单元。

在执行你正在testing的动作之前,用一个“完整性检查”断言来validation状态是一个古老的技术。 我通常会把它们写成testing脚手架,向我自己certificatetesting符合我的期望,并在稍后将它们移除以避免testing脚手架混乱。 有时候,把脚手架留下来帮助testing起到叙述的作用。

我已经阅读过这个技术 – 可能来自你btw – 但我不使用它; 主要是因为我习惯于unit testing的三Aforms。

现在,我开始好奇了,还有一些问题:你如何编写testing,在红 – 绿 – 红 – 绿 – 重构循环之后,你是否会导致这个断言失败,或者之后添加?

你有时会失败,也许在你重构代码之后? 这告诉你什么? 也许你可以分享一个有用的例子。 谢谢。

在调查失败的testing时,我已经做了这个。

在相当多的头部划伤之后,我确定原因是在“安排”期间所称的方法不能正常工作。 testing失败是误导性的。 安排后我加了一个断言。 这使得testing失败的地方突出了实际问题。

如果testing的编排部分太长而且复杂,我认为这里也有代码味道。

一般来说,我很喜欢“安排,行动,声明”,并将其作为我个人的标准。 然而,它没有提醒我要做的一件事就是,在断言完成后,解散我安排的事情。 在大多数情况下,这并不会引起太多的烦恼,因为大多数事情都是通过垃圾收集等方式自动消失的。但是,如果您build立了与外部资源的连接,那么您完成后可能需要closures这些连接你的断言,或者你有许多服务器或昂贵的资源,在那里坚持连接或重要的资源,它应该能够给别人。 如果您是那些在一次或多次testing之后不使用TearDown或TestFixtureTearDown进行清理的开发人员,这一点尤其重要。 当然,“安排,行动,断言”对于我未能完成我所打开的东西并不负责。 我只提到这个“陷阱”,因为我还没有find一个好的“A字”的同义词“处置”推荐! 有什么build议么?

看一看维基百科的合同devise 。 “安排 – 行动 – 断言圣三一”是对一些相同的概念进行编码的一种尝试,并且是关于certificate程序正确性的。 从文章:

 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, eg for time or space used 

花费在build立这个项目上的努力和它所增加的价值之间有一个权衡。 AAA对于所需的最小步骤是一个有用的提示,但不应该阻止任何人创build额外的步骤。

取决于你的testing环境/语言,但通常情况下,如果编排部分中的某些内容失败,则抛出exception,testing将无法显示,而不是启动Act部分。 所以不,我通常不使用第二个断言部分。

另外,如果你的Arrange部分相当复杂,并不总是抛出一个exception,你也许可以考虑用一些方法来包装它,并为它编写一个自己的testing,所以你可以确定它不会失败抛出exception)。

我现在正在做这个。 AAAA的另一种

 Arrange - setup Act - what is being tested Assemble - what is optionally needed to perform the assert Assert - the actual assertions 

更新testing示例:

 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为断言,我叫ASSEMBLE断言。 这是为了防止混淆ARRANGE部分

ASSERT应该只包含断言。 这使得ASSEMBLE在设置断言的ACT和ASSERT之间进行。

最后,如果你在排列方面失败,你的testing是不正确的,因为你应该有其他的testing,以防止/发现这些微不足道的错误。 因为对于我提出的场景,应该已经有其他的testingREAD和CREATE的testing。 如果您创build了“Guard Assertion”(警卫声明),则可能会破坏DRY并进行维护。

我不使用这种模式,因为我认为这样做是:

 Arrange Assert-Not Act Assert 

可能是毫无意义的,因为据说你知道你的Arrange部分工作正常,这意味着无论在Arrange部分必须testing还是很简单,不需要testing。

用你的答案的例子:

 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)); } 

如果你真的想在这个例子中testing一切,试试更多的testing…例如:

 public void testIncludes7() throws Exception { Range range = new Range(0, 5); assertFalse(range.includes(7)); } public void testIncludes5() throws Exception { Range range = new Range(0, 5); assertTrue(range.includes(5)); } public void testIncludes0() throws Exception { Range range = new Range(0, 5); assertTrue(range.includes(0)); } public void testEncompassInc7() throws Exception { Range range = new Range(0, 5); range.encompass(7); assertTrue(range.includes(7)); } public void testEncompassInc5() throws Exception { Range range = new Range(0, 5); range.encompass(7); assertTrue(range.includes(5)); } public void testEncompassInc0() throws Exception { Range range = new Range(0, 5); range.encompass(7); assertTrue(range.includes(0)); } 

因为否则,你错过了很多错误的可能性…例如,包括后,范围只包括7,等等…也有testing范围的长度(以确保它不包括一个随机值),和另一组testing完全是为了试图包含范围内的5个……我们期望什么 – 包含的例外或者范围是不变的?

无论如何,问题的关键在于,如果在你想要testing的行为中有任何假设,把它们放在自己的testing中,是吗?

我用:

 1. Setup 2. Act 3. Assert 4. Teardown 

因为一个干净的设置是非常重要的。

Interesting Posts