assert() and Unit Testing

When discussing testing, we outlined that we want to 1) develop a suite of tests (i.e., a testsuite), 2) run all tests with a single command, and 3) have regression testing automatically. We also want tests to be as simple to create as possible once we understand the test case.

Many frameworks and tools are available to help create, manage, and run test cases. The type of tests we will write are unit tests; for these, there are unit-testing frameworks. One of the first and most popular was JUnit for Java programs. JUnit is based on SUnit for Smalltalk by Kent Beck. The framework has been adapted into unit-testing frameworks for multiple languages, collectively known as xUnit. cases.

Learning a full unit-testing framework is very useful but can be complex. Therefore, we will create unit tests using features already built into most programming languages.

First, let's examine a single test. In this case, we will test a simple add function, add(int n1, int n2); declared in the include file add.hpp:

First, we set up any variables we need. Then, we call the function we are testing and store the result. To check the result, we use the assert() macro. An assertion in programming is something that should always be true. The assert() takes a condition. If, during runtime, the condition is true, i.e., we "passed the assertion", then the program goes on. If the condition is false, i.e., we "failed the assertion". The test case above passes successfully. So, let's look at a test case that fails.

This test should pass if the add() function is implemented correctly. But this is what happens when we run it:

When an assert() fails, it prints out an error message with the condition, function name, file number, and line number where the assertion failed.`

So, when an assertion fails, we do not get the expected result. Either our test case is incorrect (could happen, but not as likely), or the function add() is not working correctly (more likely). A glance at the implementation of the add() shows us the problem:

A couple of notes about using assert() in this way:

The other challenge is managing the test cases. Here, we have one test case per file, and each test case is a separate program. There are situations, especially when using external test management tools (e.g., ctest), where this is acceptable. However, for our purposes, it is difficult to build and run all the test cases. Another approach is to combine the test cases into one test program and build and run that single test program. Here is our combined test program:

In this setup, each test case is a separate block statement within the main program. Using a distinct block for each test case allows us to reuse variable names and easily create new test cases by copying and pasting. We only compile and run one test program, making file management simpler. Even if we choose to have multiple test files, we can still include multiple test cases in each file.

This approach has a drawback. When an assert() fails, the program stops, so subsequent test cases don't run. To work around this:

The assert() Macro

One example implementation of implementing an assert: