My favorite unit testing framework: Cgreen

13 Feb 2016

It is difficult to overstate the value of test driven development (TDD) for software development. Of course we end up with a comprehensive, fast, automated test suite for our software project but the benefits of TDD often go much beyond that. Oftentimes the design of the software component being developed improves as a by-product. TDD leads to more modular designs because we are forced to write tests for each component in isolation from other components. Unit testing frameworks are essential for deriving maximum value from TDD. Good unit testing frameworks allow us to write tests quickly with minimal boilerplate, result in great error messages if a test fails, and they allow us to run subsets of tests. In several of my side projects I’ve been using Cgreen as a unit testing framework and it is quickly becoming my favorite tool for the job. In this post I’ll outline some of the things I really like about Cgreen.

Cgreen is a plain C unit testing framework

First off Cgreen is a native plain C library. Incidentally, that happened to be the reason why I initially ran into Cgreen. My other two go-to testing frameworks, gtest and boost unit test framework, require C++. Now, Cgreen can also be used for unit testing C++ projects, but the majority of my experience has been with plain C projects.

Very fast compile times

My favorite Cgreen feature is the speed with which I can rebuild and rerun my test suites. This is a pleasant change especially compared to C++ unit testing frameworks that often have a large header component. Fast compile times are huge if you’re doing TDD. You need to get feedback from your test suite as quickly as possible. Usually the goal is to be able to build and run the test suite in less than a second, the faster the better. For example, rebuilding the entire test suite of beam laser takes about 0.6s on my ancient laptop.

Great syntax for test specification

Most C++ unit testing frameworks (including gtest and boost unit test) are in the tradition of jUnit. Cgreen breaks with that tradition. It’s syntax is heavily influenced by behavior driven design (BDD). As such, it has a true focus on making it so that the test read like a natural language specification.

As an example, consider the following excerpt from the test suite for ensembles of particles in BeamLaser:

#include <cgreen/cgreen.h>
#include <Ensemble.h>

static struct BLEnsemble ensemble;

Describe(Ensemble)
BeforeEach(Ensemble) {}
AfterEach(Ensemble) {}

Ensure(Ensemble, initiallyHasZeroSize) {
  blEnsembleInitialize(10, 4, &ensemble);
  assert_that(ensemble.numPtcls, is_equal_to(0));
  blEnsembleFree(&ensemble);
}

The Describe line introduces the subject under test. Behind the scenes, this creates a test context with which the Ensemble unit tests can be registered. The BeforeEach and AfterEach lines define code that is to be executed at the beginning and end of each unit test. This is similar to the setup and teardown methods of fixtures in jUnit style testing frameworks. Next we have a unit test, Ensure(Ensemble, initiallyHasZeroSize). Note how natural the test specification reads: assert_that(ensemble.numPtcls, is_equal_to(0)).

Check out the cgreen documentation for more examples of Cgreen test cases. The documentation also explains how to use other advanced features like mocks.

The beautiful syntax for test specification goes along with great error reporting in case something goes wrong. For example, if we change the is_equal_to(0) in the test example to is_equal_to(1) to artificially break the test we get the following output:

Running "main" (4 tests)...
/home/scratch/BeamLaser/tests/testEnsemble.c:12: Failure: initiallyHasZeroSize 
	Expected [ensemble.numPtcls] to [equal] [1]
		actual value:			[0]
		expected value:			[1]

Completed "main": 2 passes, 1 failure, 0 exceptions in 17ms.

The error report contains all the information we need to debug this test failure.

Each test case is run in a separate process

By default, Cgreen forks a process for each test case. This way, test cases run in complete isolation from one another. One of them crashing does not affect the remaining tests or the test runner.

To be honest, sometimes this can be a bit of a problem, too. For example, this behavior can get in the way when you’re trying to run your tests in a debugger. If you’re setting a breakpoint in one of the test cases you’ll be surprised to see that the debugger never stops at that breakpoint because that happens in a different process. A process that the debugger isn’t attached to.

An enhancement is in the pipeline where you can disable forking of new processes for test cases.

Responsive developer community

The lead developers of Cgreen are super friendly and helpful. When I first started using Cgreen, Matt spend an hour pair programming with me to show me the ropes. That despite the fact that we had never met before. The Cgreen developers respond quickly to bug reports or feature requests. There is a user’s mailing list on sourceforge but in my experience you’ll get a response more quickly if you open a ticket on github.