102

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.

Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
Carl Manaster
  • 39,912
  • 17
  • 102
  • 155

14 Answers14

136

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.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 1
    +1, very good answer. The last part is especially important, because it shows that you can guard things as a separate unit test. – murrekatt Sep 21 '11 at 12:28
  • 4
    I've generally done it this way too but there's a problem with having a separate test to ensure preconditions (especially with a large codebase with changing requirements) - the precondition test will get modified over time and get out of sync with the 'main' test that presupposes those preconditions. So the preconditions may be all fine and green but those preconditions aren't satisfied in the main test, which now always shows green and fine. But if the preconditions were in the main test, they would have failed. Have you come across this problem and come across a nice solution for it? – nchaud May 27 '14 at 09:37
  • 2
    If you change your tests a lot, you may [have other problems](http://blog.ploeh.dk/2013/04/02/why-trust-tests), because that'll tend to make your tests less trustworthy. Even in the face of changing requirements, consider [designing code in an append-only fashion](http://blog.ploeh.dk/2012/01/03/SOLIDisAppend-only). – Mark Seemann May 27 '14 at 11:37
  • @MarkSeemann You are right, that we have to minimize the repetition, but on the other side **there can be a lot of things, which can impact the Arrange for the specific test, although test for Arrange itself would pass through.** For Example the clean up for Arrange test or after another Test was faulty and Arrange wouldn't be the same as in Arrange test. – Rekshino May 12 '17 at 09:47
34

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

Ole Lynge
  • 4,457
  • 8
  • 43
  • 57
10

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.

Carl Manaster
  • 39,912
  • 17
  • 102
  • 155
  • Thanks for coming back with an example, Carl. Well, in the red part of the TDD cycle, until the encompass() really does something; the first assertion is pointless, it is only a duplication of the second. At green, it begins being useful. It is getting sense during refactoring. It could be nice to have a UT framework that does this automatically. – philant Jul 01 '09 at 19:35
  • Suppose you TDD that Range class, won't there be another failing test testing the Range ctor, when you'll break it ? – philant Jul 01 '09 at 19:37
  • 1
    @philippe: I'm not sure I understand the question. The Range constructor and includes() have their own unit tests. Could you elaborate, please? – Carl Manaster Jul 01 '09 at 19:44
  • 1
    For the first assertFalse(range.includes(7)) assertion to fail you need to have a defect in the Range Constructor. So I meant to ask whether the tests for the Range constructor won't break at the same time as that assertion. And what about asserting _after the Act_ on another value: e.g. assertFalse(range.includes(6)) ? – philant Sep 11 '09 at 11:38
  • 1
    Range construction, to my mind, comes before functions like includes(). So while I agree, only a faulty constructor (or a faulty includes()) would cause that first assertion to fail, the constructor's test wouldn't include a call to includes(). Yes, all the functions up to the first assertion are already tested. But this initial negative assertion is communicating something, and, to my mind, something useful. Even if every such assertion passes when it is initially written. – Carl Manaster Sep 11 '09 at 16:05
  • I would expect that for thoroughness you should also have asserts after the range.Encompass to ensure that the new range does not include -1 or 8 but does still include 0? – supercat Jul 08 '10 at 17:04
  • @supercat: certainly it does no harm, but it's somewhat orthogonal to what I'm trying to represent here: that you first assert the condition hasn't been established; then act; then assert the condition has been established. That you are really testing that Act is what causes the desired outcome. – Carl Manaster Jul 08 '10 at 18:23
  • The various post-action assertions are indeed irrelevant to the difference between arrange-assert-act-assert and arrange-act-assert, but they shouldn't be left out of any real test. I would think you'd want to test all boundary conditions before and after, in cases where the boundaries should change and where they should not, so you can demonstrate that you've tested that a function can indeed change all the assertions that it can. – supercat Jul 09 '10 at 15:36
9

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.

Sámal Rasmussen
  • 2,887
  • 35
  • 36
5

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.

Valamas
  • 24,169
  • 25
  • 107
  • 177
2

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));
}
Marcel Valdez Orozco
  • 2,985
  • 1
  • 25
  • 24
  • I'm afraid you do not really understand my question. The initial assert is not about testing Arrange; it is simply ensuring that the Act is what brings about the state being asserted at the end. – Carl Manaster Aug 26 '12 at 06:14
  • And my point is that, whatever you put in the Assert-Not part, is already implied in the Arrange part, because the code in the Arrange part is thoroughly tested and you already know what it does. – Marcel Valdez Orozco Aug 26 '12 at 19:38
  • But I believe there is value in the Assert-Not part, because you are saying: Given that the Arrange part leaves 'the world' in 'this state' then my 'Act' will leave 'the world' in this 'new state'; and if the implementation of the code the Arrange part depends on, changes, then the test will break too. But again, that might be against DRY, because you (should) also have tests for whatever code you are depending in the Arrange part. – Marcel Valdez Orozco Aug 26 '12 at 19:45
  • Maybe in projects where there are several teams (or a big team) working on the same project, such a clause would be pretty useful, otherwise I find it to be unnecessary and redundant. – Marcel Valdez Orozco Aug 26 '12 at 19:46
  • Probably such a clause would be better in Integration tests, System Tests or Acceptance Tests, where the Arrange part usually depends on more than one component, and there are more factors that could cause the initial state of 'the world' to change unexpectedly. But I don't see a place for it in Unit tests. – Marcel Valdez Orozco Aug 26 '12 at 19:52
  • Such is my opinion, I don't mean for it to be the right or universal answer, but that's what I do. I usually refactor the Arrange part in Integration tests and add a test for it, and reuse that same code in all my related Integration tests, instead of adding the Arrange-Not part in all Integration tests. – Marcel Valdez Orozco Aug 26 '12 at 19:54
  • I just thought about another place where Assert-Not or Assume, like they said in a previous answer, is useful. When testing new Features on top of legacy code (no previous tests). – Marcel Valdez Orozco Aug 26 '12 at 20:54
1

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.

Dave W. Smith
  • 24,318
  • 4
  • 40
  • 46
1

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.

philant
  • 34,748
  • 11
  • 69
  • 112
  • I typically don't force the initial assertion to fail - after all, it shouldn't fail, the way a TDD assertion should, before its method is written. I _do_ write it, when I write it, _before_, just in the normal course of writing the test, not afterward. Honestly, I can't remember it failing - maybe that suggests it's a waste of time. I'll try to come up with an example, but I don't have one in mind at present. Thanks for the questions; they're helpful. – Carl Manaster Jun 20 '09 at 19:31
1

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.

David Clarke
  • 12,888
  • 9
  • 86
  • 116
1

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.

WW.
  • 23,793
  • 13
  • 94
  • 121
  • A minor point: I would consider the too-complicated Arrange more of a design smell than a code smell - sometimes the design is such that only a complicated Arrange will allow you to test the unit. I mention it because that situation wants a deeper fix than a simple code smell. – Carl Manaster Aug 05 '10 at 12:51
1

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?

John Tobler
  • 404
  • 2
  • 14
  • 1
    @carlmanaster, you are actually close enough for me! I'm sticking that in my next TestFixture to try it on for size. It's like that little reminder to do what your mother should have taught you: "If you open it close it! If you mess it up, clean it up!" Maybe someone else can improve on it but at least it starts with an "a!" Thanks for your suggestion! – John Tobler Aug 05 '11 at 22:51
  • 1
    @carlmanaster, I did give "Annul" a try. It's better than "teardown," and it kind of works, but I'm still looking for another "A" word that sticks in my head as perfectly as "Arrange, Act, Assert." Maybe "Annihilate?!" – John Tobler Aug 16 '11 at 19:07
  • 1
    So now, I have "Arrange, Assume, Act, Assert, Annihilate." Hmmm! I'm overcomplicating things, huh? Maybe I'd better just K.I.S.S. off and go back to "Arrange, Act, and Assert!" – John Tobler Aug 16 '11 at 19:17
  • 1
    Maybe use an R for Reset? I know its not an A, but it sounds like a pirate saying: Aaargh! and Reset rhymes with Assert :o – Marcel Valdez Orozco Aug 26 '12 at 05:21
0

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).

schnaader
  • 49,103
  • 10
  • 104
  • 136
0

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?

Andrew
  • 31
  • 2
0

I use:

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

Because a clean setup is very important.

kame
  • 20,848
  • 33
  • 104
  • 159