Gotta catch 'em all
Mar 30, 2020
Giovanni Charles
2 minute read
Tags:

Great packages have great test suites. Packages with C++ are not exceptions. You might be surprised to find how easy it is to add C++ unittests to your package.

This post will focus on testthat’s Catch integration. There are plenty of C++ testing frameworks, each with their own pros and cons. The benefit of this approach is the really low setup cost and no dependency management.

Set up

The first step is to add some boilerplate to your package. Enter testthat::use_catch(). Make sure to follow the instructions in the command output to configure your package correctly.

Now you can run all of your tests with devtools::test() or R CMD check!

Writing your tests

As soon as you start writing tests you may say, “hang on a minute, where are all my assertions?”. You would be right to worry. Testthat.h only provides 4 assertions:

  • expect_true
  • expect_false
  • expect_error
  • expect_error_as

That leaves a lot to be desired, not to mention unhelpful error messages when your tests fail.

At this point it’s helpful to explain that testthat.h simply gives you sugar to make a catch test look like a more familiar testthat test. If you dig into the header you’ll find that test_that is defined as CATCH_SECTION and expect_true is defined as CATCH_CHECK.

You can find a much larger list of straightfoward and extensive Catch assertions on github. And they can be accessed through the testthat API by prefixing them with CATCH_.

So the secret here is to use Catch directly. Let’s say, for example, that you would like to test your own square root function sqrt. You could write the test like so:

#include <testthat.h> /* context, test_that, CATCH_REQUIRE, Approx */
#include "my_math.h" /* sqrt */

context("My math library") {
    test_that("Squareroot of 2 is correct") {
        CATCH_REQUIRE(sqrt(2) == Approx(1.41421356237));
    }
}

Notice how catch provides a helpful Approx class to handle the comparison of real numbers.

And when I stub my sqrt function, I get a helpful error message like below:

test-math.cpp:XX: FAILED:
  CATCH_REQUIRE( sqrt(2) == Approx(1.41421356237) )
with expansion:
  2 == Approx(1.41421356237)

Logging

When making your tests pass, it’s useful to see what’s going on with your code. Unfortunately the default test runner swallows the standard output stream. Even CATCH_INFO fails to release any output onto the screen.

To keep your logs visible you have to write to standard error (Rcpp::cerr) or to a file. So it is useful to have a configurable logger early in development.

Happy coding!




comments powered by Disqus