views:

332

answers:

8

Greetings,

I've been adding unit tests to some legacy C++ code, and I've run into many scenarios where an assert inside a function will get tripped during a unit test run. A common idiom that I've run across is functions that take pointer arguments and immediately assert if the argument is NULL.

I could easily get around this by disabling asserts when I'm unit testing. But I'm starting to wonder if unit tests are supposed to alleviate the need for runtime asserts. Is this a correct assessment? Are unit tests supposed to replace runtime asserts by happening sooner in the pipeline (ie: the error is caught in a failing test instead of when the program is running).

On the other hand, I don't like adding soft fails to code (eg: if(param == NULL) return false;). A runtime assert at least makes it easier to debug a problem in case a unit test missed a bug.

Thanks!

+2  A: 

IMO asserts and unit tests are fairly independent concepts. Neither of them can replace the other.

Asserts are meant to ensure that certain conditions / invariants are always valid during the lifetime of the program. Or to be more precise, to ensure that if such a condition gets broken, we get to know about it ASAP, as close to the root cause of the problem as possible.

Unit tests are meant to ensure that certain parts of the code work properly in isolation from the rest of the program.

You can't ensure by unit testing a class that its environment is always going to fulfill its part of the contract under real life circumstances. More so as said environment includes future developers who might not have a clue about the interface contract governing the usage of this class (be it implicit or carefully documented). And also a host of other software and hardware components, which may change and/or get broken anytime, in a manner uncontrollable by the developers of this specific program.

Péter Török
+12  A: 

A runtime assert at least makes it easier to debug a problem in case a unit test missed a bug.

This is a pretty fundamental point. Unit tests are not meant to replace assertions (which IMHO are a standard part of producing good quality code), they're meant to complement them.

Secondly, lets say you have a function Foo which asserts that it's parameters are valid.
In your unit test for Foo you can make sure you only supply valid parameters, so you think you're alright.
6 months down the track some other developer is going to call Foo from some new piece of code (which may or may not have unit tests), and at that point you'll be very grateful you put those asserts in there.

Orion Edwards
+3  A: 

Usually, you should not be able to trip asserts as they are supposed to catch "impossible situations." If an assert fires, that should indicate a bug.

One exception to this rule is that many developers use asserts to verify that arguments are valid. This is okay provided there is also a backup for builds without asserts:

assert(arg != 0);
if (arg != 0)
    throw std::runtime_error();

This way, if a bad argument only happens under specific conditions (i.e. out in the field), it will still be caught.

If you code this way, you can turn off asserts and write negative tests to make sure bad arguments are caught.

R Samuel Klatchko
If you want it that way (I don't btw), why not simply define your assert to throw a std::runtime_error() if DEBUG is not defined?
Viktor Sehr
@ViktorSehr - because the behavior of assert is standardized and I think it would be confusing to change the behavior.
R Samuel Klatchko
In which case, wouldn't it be possible to use a wrapper? Or just throw a runtime error regardless of build settings?
UncleBens
+6  A: 

If your unit test code is correct, then the assert is a bug that the unit test has uncovered.

But it is far more likely that your unit test code is violating the constraints of the units it tests - your unit test code is buggy!

Commentators have raised the point that:

Consider a unit test that validates the function handles invalid input properly.

The assert is the programmer's way of handling invalid input, by aborting the program. By aborting, the program is functioning properly.

Asserts are only in the debug builds (they are not compiled if the NDEBUG macro is defined) and it's important to test that the program does something sensible in release builds. Consider running your invalid parameters unit-test on release builds.

If you want both worlds - checking asserts fire in debug builds - then you want your in-thread unit test harness to capture these asserts. You can do this by providing your own assert.h rather than using the system one; a macro (you'd want the _LINE_ and _FILE_ and ##expr) would call a function you wrote, which can throw a custom AssertionFailed when run in a unit test harness. This obviously does not capture asserts compiled into other binaries you link against but did not compile from source with your custom asserter. (I'd recommend this over providing your own abort(), but that's another approach you might consider to achieve the same end.)

Will
Could the downvoter please explain why?
Will
I didn't down vote you but I think the problem is that a valid unit test can trigger an assert. Consider a unit test that validates the function handles invalid input properly. The assert will trigger because the input is invalid but the unit test is very much proper as its testing this case.
chollida
It is much more likely be that the assert has exposed an error in the unit test. I didn't downvote you, BTW.
anon
Could you elaborate on why you think it's more likely that the unit test is violating the constraint of the code it's testing? If a unit test simply calls a class function and trips an assert, doesn't that suggest the code being tested might have an issue?
lhumongous
@lhumongous If you are testing a function for how it handles 'invalid parameters', you can't really complain that the programmer put in asserts to catch that. Asserts are used to bring early attention to compromised constraints. Deviating slightly, its my opinion that unit tests for testing illegal parameters are not as useful as fuzzers and other system tests. Compile with -Wall, scan with lint/coverity, run your program in valgrind/purify, litter it with asserts, and do an infinite monkeys test. Thats how you smoke out bugs.
Will
+3  A: 

Assertions catch when your code is being used incorrectly (violating its constraints or preconditions - using a library without initialization, passing NULL to a function that won't accept it, etc.).

Unit tests verify that your code does the right thing as long as it's being used correctly.

Assertions also catch when your code has entered a state which you believed to be impossible (which, since it is believed to be impossible, can't be unit tested).

Interestingly, at least one C++ unit testing framework (Google Test) supports death tests, which are unit tests that verify that your assertions work properly (so that you can know your assertions are doing their job of catching invalid program states).

Josh Kelley
If a function doesn't accept NULLs, shouldn't it be called with a reference instead of a pointer? Unless we're talking about C.
Chris Bednarski
@KNoodles: char* or const char*; adherence to existing coding style or coding standards; typedef void * HANDLE...
Josh Kelley
A: 

Personally I don't tend to use asserts as, as you've discovered, they often don't play nicely with unit tests. I tend to prefer throwing exceptions in situations where others would often use asserts. These checks, and the exceptions that are thrown on failure, are present in both debug and release builds and I find that they often catch things that 'can't possibly happen' even in release builds (which asserts often don't as they're often compiled out). I find it works better for me and means that I can write unit tests that expect the exception to be thrown on invalid input rather than expecting an assertion to fire.

Lots of people don't agree, see 1, 2, etc but I don't care. Avoiding asserts and using exceptions instead works well for me and helps me to produce robust code for clients...

Len Holgate
+1  A: 

Two possibilities here:

1) The behaviour of the function is defined (by its own interface explicitly, or by general rules for the project) when the input is null. The unit test therefore needs to test this behaviour. So you need a handler to run a process that runs the test case, and the handler validates that the code tripped the assertion and aborted, or you need to mock assert somehow.

2) The behaviour of the function is not defined when the input is null. The unit test therefore needs to not pass in null - a test is a client of the code too. You can't test something if there's nothing in particular that it's supposed to do.

There is no third option, "the function has undefined behaviour when passed a null input, but the tests pass in null anyway, just in case something interesting happens". So I don't see how, "I could easily get around this by disabling asserts when I'm unit testing" helps at all. Surely the unit tests will cause the function under test to dereference a null pointer, which isn't any better than tripping an assert. The whole reason the asserts are there is to stop something even worse from happening.

In your case, perhaps (1) applies in DEBUG builds, and (2) applies in NDEBUG builds. So perhaps you could run the null-input tests only on debug builds, and skip them when testing the release build.

Steve Jessop
+1  A: 

I only use asserts to check for things that "can never happen". If an assert fires, then a programming mistake has been made somewhere.

Let's say a method takes the name of an input file, and a unit test feeds it the name of a non-existent file to see if a "file not found" exception is thrown. That's not something that "can never happen". It is possible to not find a file at runtime. I would not use an assert in the method to verify that the file was found.

However, the string length of the file name argument must never be negative. If it is, then there is a bug somewhere. So, I might use an assert to say "this length can never be negative". (It's just an artificial example.)

In the case of your question, if the function asserts != NULL, either the unit test is wrong and should not be sending in NULL, because this will never happen, or, the unit test is valid and NULL might possibly be sent in, and the function is wrong and should not be asserting != NULL and must instead handle that condition.

Jim Flood