Gem #160 : Developing unit tests with GNATtest
by Vasiliy Fofanov —AdaCore
Let's get started...
When you invoke GNATtest, it analyzes your program and identifies all library-level subprograms (that is, subprograms declared inside package specs); for each such subprogram, GNATtest creates a unit test skeleton. It also takes care of creating a fully compilable test harness that will encapsulate these unit test skeletons, as well as your own code. When you compile and run the harness, it calls each of the unit tests one by one, then analyzes the results and reports them. All you'll have to do yourself is replace the unit test skeletons with the actual unit test code.
The GNAT distribution provides several examples of GNATtest use; these can be found at [install root]/share/examples/gnattest. Let's use the example "simple" to see what the typical GNATtest use might be.
If you open the Simple project, you will see that it contains a single package that currently declares a single subprogram Inc. Let's develop a test suite for it.
One nice thing about GNATtest is that it's integrated with GPS. So even if you normally call the tool from the command line (details, as usual, can be found in the GNAT User's Guide) -- you don't have to. Instead, you can get started with your test suite literally with one click.
To do that, simply select the GPS command "Tools -> GNATtest -> Generate unit test setup". In the project explorer you'll see your project replaced by the automatically generated project hierarchy, in which your own project has become just one of the dependencies.
Now you can quickly jump to the test-case code by right-clicking on the Inc routine and selecting "GNATtest -> Go to test case". You'll see that currently the test merely contains a stub:
AUnit.Assertions.Assert (Gnattest_Generated.Default_Assert_Value, "Test not implemented.");
You can already build and run your harness, but all that the above code will do is cause the test to fail:
$ test_runner simple.ads:7:4: corresponding test FAILED: Test not implemented. (simple-test_data-tests.adb:25) 1 tests run: 0 passed; 1 failed; 0 crashed.
Let's implement the test, by replacing Gnattest_Generated.Default_Assert_Value with a Boolean expression that actually calls our subprogram and verifies the result, and provides an informative message:
AUnit.Assertions.Assert (Inc (1) = 2, "Incrementation failed.");
After recompiling the test driver you can see that the test now passes.
Keeping the test suite up to date
You can call GNATtest any number of times on an already generated harness -- it will never overwrite test routine bodies, so you won't lose your work. At the same time, it may add more unit test skeletons if you were to add more subprograms to your code.
Let's see how this works. Uncomment the second subprogram and its body in the source files for the project Simple, then run GNATtest again, and compile and run the test driver.
You will see that the old test still passes but there is now a new unimplemented test:
$ test_runner simple.ads:7:4: corresponding test PASSED simple.ads:9:4: corresponding test FAILED: Test not implemented. (simple-test_data-tests.adb:46) 2 tests run: 1 passed; 1 failed; 0 crashed.
GNATtest will also warn you that some tests have become dangling if you change the parameter profile or delete subprograms for which unit tests have already been written.
As you can see, GNATtest provides a lightweight and readily available solution to get started with developing unit tests. Unless you already have a unit-testing infrastructure that works for you (oh, and by the way, if you do, did you know that you can also merge existing unit tests into the GNATtest-generated harness?) -- we encourage you to try out GNATtest!