views:

571

answers:

7

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.

A: 

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
+1  A: 

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
+7  A: 

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
+1  A: 

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.

philippe
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
+2  A: 

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
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.
philippe
Suppose you TDD that Range class, won't there be another failing test testing the Range ctor, when you'll break it ?
philippe
@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
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)) ?
philippe
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
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
@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
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
+1  A: 

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
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
+2  A: 

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
Nice! I like a fourth - and different - and accurate - "A". Thanks!
Carl Manaster