应该是“安排-断言-行动-断言”吗?

发布于 2024-07-25 11:45:34 字数 490 浏览 9 评论 0原文

关于 Arrange-Act-Assert 的经典测试模式,我经常发现自己添加了反断言在法案之前。 这样我就知道传递的断言确实是作为操作的结果传递的。

我认为它类似于红绿重构中的红色,只有当我在测试过程中看到红色条时,我才知道绿色条意味着我已经编写了有所作为的代码。 如果我编写了一个通过测试,那么任何代码都可以满足它; 类似地,对于 Arrange-Assert-Act-Assert,如果我的第一个断言失败,我知道任何法案都会通过最终的断言 - 因此它实际上并没有验证有关该法案的任何内容。

您的测试遵循这种模式吗? 为什么或者为什么不?

更新澄清:初始断言本质上与最终断言相反。 这并不是断言 Arrange 有效;而是说 Arrange 有效。 这是一个断言,该法案尚未发挥作用。

Regarding the classic test pattern of Arrange-Act-Assert, I frequently find myself adding a counter-assertion that precedes Act. This way I know that the passing assertion is really passing as the result of the action.

I think of it as analogous to the red in red-green-refactor, where only if I've seen the red bar in the course of my testing do I know that the green bar means I've written code that makes a difference. If I write a passing test, then any code will satisfy it; similarly, with respect to Arrange-Assert-Act-Assert, if my first assertion fails, I know that any Act would have passed the final Assert - so that it wasn't actually verifying anything about the Act.

Do your tests follow this pattern? Why or why not?

Update Clarification: the initial assertion is essentially the opposite of the final assertion. It's not an assertion that Arrange worked; it's an assertion that Act hasn't yet worked.

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

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

发布评论

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

评论(14

美人骨 2024-08-01 11:45:35

Arrange-Assert-Act-Assert 测试始终可以重构为两个测试:

1. Arrange-Assert

2. Arrange-Act-Assert

一个测试将仅断言在 Arrange 阶段设置的测试,第二个测试将仅断言行动阶段发生的事情。

这样做的好处是可以提供更准确的反馈,判断是安排阶段还是行动阶段失败,而在原始的 Arrange-Assert-Act-Assert 中,这些是混杂在一起的,您必须深入挖掘并了解准确检查哪些断言失败以及失败的原因,以便了解失败的是安排还是行动。

它还更好地满足了单元测试的目的,因为您将测试分成更小的独立单元。

An Arrange-Assert-Act-Assert test can always be refactored into two tests:

1. Arrange-Assert

and

2. Arrange-Act-Assert

The first test will only assert on that which was set up in the Arrange phase, and the second test will only assert for that which happened in the Act phase.

This has the benefit of giving more precise feedback on whether it's the Arrange or the Act phase that failed, while in the original Arrange-Assert-Act-Assert these are conflated and you would have to dig deeper and examine exactly what assertion failed and why it failed in order to know if it was the Arrange or Act that failed.

It also satisfies the intention of unit testing better, as you are separating your test into smaller independent units.

春风十里 2024-08-01 11:45:35

我现在正在做这个。 不同类型的 AAAA

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 的读取的原因是它不是 Act 的一部分。 该行为只是改变和拯救。 所以实际上,ARRANGE ReadUpdated 进行断言,我正在调用 ASSEMBLE 进行断言。 这是为了防止混淆 ARRANGE 部分

ASSERT 应该只包含断言。 这使得 ASSEMBLE 位于 ACT 和 ASSERT 之间,用于设置断言。

最后,如果您在安排中失败,则您的测试不正确,因为您应该进行其他测试来防止/发现这些微不足道的错误。 因为对于我提出的场景,应该已经有其他测试来测试 READ 和 CREATE。 如果您创建“Guard Assertion”,您可能会破坏 DRY 并创建维护。

I am now doing this. A-A-A-A of a different kind

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

Example of an update test:

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

The reason is so that the ACT does not contain the reading of the ReadUpdated is because it is not part of the act. The act is only changing and saving. So really, ARRANGE ReadUpdated for assertion, I am calling ASSEMBLE for assertion. This is to prevent confusing the ARRANGE section

ASSERT should only contain assertions. That leaves ASSEMBLE between ACT and ASSERT which sets up the assert.

Lastly, if you are failing in the Arrange, your tests are not correct because you should have other tests to prevent/find these trivial bugs. Because for the scenario i present, there should already be other tests which test READ and CREATE. If you create a "Guard Assertion", you may be breaking DRY and creating maintenance.

凡尘雨 2024-08-01 11:45:35

在执行正在测试的操作之前加入“健全性检查”断言来验证状态是一种旧技术。 我通常将它们编写为测试脚手架,以向自己证明测试符合我的预期,并稍后删除它们以避免测试脚手架造成测试混乱。 有时,保留脚手架有助于测试发挥叙事作用。

Tossing in a "sanity check" assertion to verify state before you perform the action you're testing is an old technique. I usually write them as test scaffolding to prove to myself that the test does what I expect, and remove them later to avoid cluttering tests with test scaffolding. Sometimes, leaving the scaffolding in helps the test serve as narrative.

ゝ偶尔ゞ 2024-08-01 11:45:35

总的来说,我非常喜欢“Arrange、Act、Assert”,并将其作为我个人的标准。 然而,它没有提醒我去做的一件事是,在断言完成后打乱我已经安排的事情。 在大多数情况下,这不会造成太多烦恼,因为大多数事情都会通过垃圾收集等自动消失。但是,如果您已经建立了与外部资源的连接,那么您可能希望在完成后关闭这些连接根据您的断言,或者您可能在某处拥有一台服务器或昂贵的资源,保留着连接或重要资源,这些资源应该能够赠送给其他人。 如果您是这样做的开发人员之一,这一点尤其重要在一项或多项测试后不要使用 TearDown 或 TestFixtureTearDown 进行清理。 当然,“Arrange、Act、Assert”并不对我未能关闭我打开的内容负责; 我只提到这个“陷阱”,因为我还没有找到一个好的“处置”同义词来推荐! 有什么建议么?

In general, I like "Arrange, Act, Assert" very much and use it as my personal standard. The one thing it fails to remind me to do, however, is to dis-arrange what I have arranged when the assertions are done. In most cases, this doesn't cause much annoyance, as most things auto-magically go away via garbage collection, etc. If you have established connections to external resources, however, you will probably want to close those connections when you're done with your assertions or you many have a server or expensive resource out there somewhere holding on to connections or vital resources that it should be able to give away to someone else. This is particularly important if you're one of those developers who does not use TearDown or TestFixtureTearDown to clean up after one or more tests. Of course, "Arrange, Act, Assert" is not responsible for my failure to close what I open; I only mention this "gotcha" because I have not yet found a good "A-word" synonym for "dispose" to recommend! Any suggestions?

山有枢 2024-08-01 11:45:35

我不使用这种模式,因为我认为做类似的事情:

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

I don't use that pattern, because I think doing something like:

Arrange
Assert-Not
Act
Assert

May be pointless, because supposedly you know your Arrange part works correctly, which means that whatever is in the Arrange part must be tested aswell or be simple enough to not need tests.

Using your answer's example:

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));
}
凯凯我们等你回来 2024-08-01 11:45:35

我已经读过有关此技术的内容 - 可能是从您那里顺便说一句 - 但我不使用它; 主要是因为我习惯了单元测试的 3A 形式。

现在,我很好奇,并且有一些问题:您如何编写测试,是否会导致此断言失败,遵循红-绿-红-绿-重构循环,还是之后添加它?

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

I've already read about this technique - possibly from you btw - but I do not use it; mostly because I'm used to the triple A form for my unit tests.

Now, I'm getting curious, and have some questions: how do you write your test, do you cause this assertion to fail, following a red-green-red-green-refactor cycle, or do you add it afterwards ?

Do you fail sometimes, perhaps after you refactor the code ? What does this tell you ? Perhaps you could share an example where it helped. Thanks.

春花秋月 2024-08-01 11:45:35

我以前在调查失败的测试时曾这样做过。

经过一番苦思冥想后,我确定原因是“排列”期间调用的方法无法正常工作。 测试失败具有误导性。 我在安排后添加了一个断言。 这使得测试在突出实际问题的地方失败。

我认为如果测试的排列部分太长太复杂,这里也会有代码味道。

I have done this before when investigating a test that failed.

After considerable head scratching, I determined that the cause was the methods called during "Arrange" were not working correctly. The test failure was misleading. I added a Assert after the arrange. This made the test fail in a place which highlighted the actual problem.

I think there is also a code smell here if the Arrange part of the test is too long and complicated.

素罗衫 2024-08-01 11:45:35

查看 Wikipedia 关于Design by Contract 的条目。 Arrange-Act-Assert 三位一体是对一些相同概念进行编码的尝试,旨在证明程序的正确性。 摘自文章:

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

在设置此功能所花费的精力与其所增加的价值之间需要进行权衡。 AAA 是对所需最少步骤的有用提醒,但不应阻止任何人创建额外的步骤。

Have a look at Wikipedia's entry on Design by Contract. The Arrange-Act-Assert holy trinity is an attempt to encode some of the same concepts and is about proving program correctness. From the article:

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

There is a tradeoff between the amount of effort spent on setting this up and the value it adds. A-A-A is a useful reminder for the minimum steps required but shouldn't discourage anyone from creating additional steps.

三生殊途 2024-08-01 11:45:35

取决于您的测试环境/语言,但通常如果 Arrange 部分中的某些内容失败,则会引发异常,并且测试无法显示它,而不是启动 Act 部分。 所以不,我通常不使用第二个 Assert 部分。

另外,如果您的 Arrange 部分非常复杂并且并不总是抛出异常,您可能会考虑将其包装在某个方法中并为其编写自己的测试,这样您就可以确保它不会失败(没有抛出异常)。

Depends on your testing environment/language, but usually if something in the Arrange part fails, an exception is thrown and the test fails displaying it instead of starting the Act part. So no, I usually don't use a second Assert part.

Also, in the case that your Arrange part is quite complex and doesn't always throw an exception, you might perhaps consider wrapping it inside some method and writing an own test for it, so you can be sure it won't fail (without throwing an exception).

野心澎湃 2024-08-01 11:45:35

如果你真的想测试示例中的所有内容,请尝试更多测试...例如:

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 等...
还有范围长度的测试(以确保它不包含随机值),以及另一组完全用于尝试包含范围内的 5 的测试......我们会期望什么 - 包含中的例外,或不改变的范围?

无论如何,重点是如果您想测试该行为中的任何假设,请将它们放入自己的测试中,是吗?

If you really want to test everything in the example, try more tests... like:

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

Because otherwise you are missing so many possibilities for error... eg after encompass, the range only inlcudes 7, etc...
There are also tests for length of range (to ensure it didn't also encompass a random value), and another set of tests entirely for trying to encompass 5 in the range... what would we expect - an exception in encompass, or the range to be unaltered?

Anyway, the point is if there are any assumptions in the act that you want to test, put them in their own test, yes?

只想待在家 2024-08-01 11:45:35

我使用:

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

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

I use:

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

Because a clean setup is very important.

小…楫夜泊 2024-08-01 11:45:34

这不是最常见的事情,但仍然很常见,有自己的名字。 这种技术称为“Guard Assertion”。 您可以在 Gerard Meszaros 所著的优秀著作《xUnit Test Patterns》(强烈推荐)的第 490 页上找到它的详细描述。

通常,我自己不会使用这种模式,因为我发现编写一个特定的测试来验证我认为需要确保的任何前提条件更为正确。 如果前提条件失败,这样的测试应该总是失败,这意味着我不需要将它嵌入到所有其他测试中。 这可以更好地隔离关注点,因为一个测试用例仅验证一件事。

对于给定的测试用例,可能需要满足许多先决条件,因此您可能需要多个 Guard Assertion。 不必在所有测试中重复这些测试,而是为每个先决条件进行一个(且仅一个)测试,可以使您的测试代码更易于维护,因为这样可以减少重复。

This is not the most common thing to do, but still common enough to have its own name. This technique is called Guard Assertion. You can find a detailed description of it on page 490 in the excellent book xUnit Test Patterns by Gerard Meszaros (highly recommended).

Normally, I don't use this pattern myself, since I find it more correct to write a specific test that validates whatever precondition I feel the need to ensure. Such a test should always fail if the precondition fails, and this means that I don't need it embedded in all the other tests. This gives a better isolation of concerns, since one test case only verifies one thing.

There may be many preconditions that need to be satisfied for a given test case, so you may need more than one Guard Assertion. Instead of repeating those in all tests, having one (and one only) test for each precondition keeps your test code more mantainable, since you will have less repetition that way.

呆橘 2024-08-01 11:45:34

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

NUnit 中有一个技术处理方法,如下例所示:
http://nunit.org/index.php?p=theory&r =2.5.7

It could also be specified as Arrange-Assume-Act-Assert.

There is a technical handle for this in NUnit, as in the example here:
http://nunit.org/index.php?p=theory&r=2.5.7

云淡月浅 2024-08-01 11:45:34

这是一个例子。

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。并且由于包含是我们正在测试的单元,我认为这是一些(小)值。

我接受我自己的答案;很多其他人误解了我的问题是关于测试设置。

Here's an example.

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
    range.encompass(7);
    assertTrue(range.includes(7));
}

It could be that I wrote Range.includes() to simply return true. I didn't, but I can imagine that I might have. Or I could have written it wrong in any number of other ways. I would hope and expect that with TDD I actually got it right - that includes() just works - but maybe I didn't. So the first assertion is a sanity check, to ensure that the second assertion is really meaningful.

Read by itself, assertTrue(range.includes(7)); is saying: "assert that the modified range includes 7". Read in the context of the first assertion, it's saying: "assert that invoking encompass() causes it to include 7. And since encompass is the unit we're testing, I think that's of some (small) value.

I'm accepting my own answer; a lot of the others misconstrued my question to be about testing the setup. I think this is slightly different.

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