SCUnit is a small, easy-to-use unit testing framework for C. With no complex configuration or setup required, it allows you to focus on the important things: Writing and executing tests.
SCUnit is written using the popular XUnit style. If you've ever worked with a framework like NUnit or JUnit before, you'll feel right at home. Even if you've never done any unit testing, you won't face a steep learning curve as SCUnit is specifically designed to be small and easy to use. All you need to get started is a compliant C23 compiler, include SCUnit in your sources and link it as a library to your test executable.
Before we go into more detail, here's a simple example of what testing with SCUnit looks like:
#include <SCUnit/scunit.h>
SCUNIT_SUITE(ExampleSuite);
SCUNIT_TEST(ExampleSuite, ExampleTest) {
Widget* widget = widget_new();
SCUNIT_ASSERT_NOT_NULL(widget);
SCUNIT_ASSERT_TRUE(widget->isAvailable);
SCUNIT_ASSERT_GREATER_OR_EQUAL(widget->capacity, 42);
widget_free(widget);
}
int main(int argc, char** argv) {
scunit_parseArguments(argc, argv);
return scunit_executeSuites();
}
Some of the key features for which you might consider using SCUnit include:
- Automatic discovery and registration of tests and suites.
- A minimal, yet useful set of assertions for checking various conditions in tests.
- Ability to group logically related tests into suites. Particularly large suites can even be distributed across multiple source files for readability.
- Support for suite or test setup and teardown functions.
- Accurate and optionally colored diagnostic output on
stdout
andstderr
that not only informs you of the status of a test, but also includes execution time measurements and some helpful context when an assertion fails. - Ability to configure SCUnit (if required at all) by passing command line arguments to the test executable.
Some other nice features that probably aren't that relevant for normal users, but might be for you if you want to dive deeper and take full advantage of SCUnit:
- A well-documented API (down to the very last implementation detail) that allows you to understand why SCUnit is written the way it is and how it is intended to be used.
- Consistent and predictable error handling using an error enumeration.
- Ability to manually register and execute suites and tests using a normal function API if you need the full control.
- Ability to replace the functions for dynamic memory management (e. g. for debugging purposes).
- A utility module for printing formatted and optionally colored strings to streams and buffers.
- A simple timer for measuring the elapsed wall or CPU time required to execute a block of code.
- A pseudorandom number generator (PRNG) based on the xoshiro256** variant.
SCUnit is written in pure C23 and does not have many dependencies besides the C standard library and a compliant C23 compiler. Specifically, it does use three features that might not be available on all platforms:
- The automatic allocation, registration and deallocation of suites and tests is implemented using
compiler-specific attributes called
__attribute__((constructor))
and__attribute__((destructor))
(or[[gnu::constructor]]
and[[gnu::destructor]]
in the new C23 syntax), which cause an annotated function to be executed before (and after)main()
itself is. These attributes are supported by GCC and Clang, but not by MSVC, which shouldn't be too much of an issue since it doesn't have adequate C23 support at the moment anyway. If you absolutely need the automatic registration in conjunction with MSVC, you should be able to find various workarounds related to linker sections fairly easily on internet. - The simple timer used by SCUnit to measure the elapsed wall and CPU time of suites and tests is
implemented using the POSIX function
clock_gettime()
. This is mainly due to the lack of other suitable and portable options in the C standard library. Generally speaking, it should be available on MacOS and Linux, but not on Windows. In the latter case, I can highly recommend MSYS2 as a solution. Another alternative would be to rely on a platform-specific, more feature-rich timer and change the underlying implementation. - Command line arguments passed to the test executable are parsed using the function
getopt_long()
, which is a GNU extension ofgetopt()
to support long command line options like--help
or--version
in addition to the standard short ones.
If your platform supports these features, you can go ahead and build SCUnit from source using the
provided Makefile. Run make help
first to see a useful overview of all options,
which should produce something like this:
Usage: make [TARGET]... [VARIABLE]...
Targets:
all Build both a static and shared library (default).
static Build only a static library.
shared Build only a shared library.
clean Remove all build artifacts.
help Display this help.
Variables:
BUILD_TYPE={debug|release} Set the build type (default = release).
SCUnit can be built and linked as a static or shared library, whichever you prefer. Run make all
or simply make
to build both (the default). If you need a variant for debugging purposes,
change the build type from release
(the default) to debug
by defining BUILD_TYPE=debug
.
Debug variants are identified by an additional d
suffix in the library name (e. g. libscunitd.a
for a static library built in debug mode). They are not optimized and contain various debug symbols
to provide a better debugging experience.
All binaries are generated in the bin directory. Here's a quick overview of the different variants that can be built (links only work after the specific variant has been built):
Build Type / Library Type | Static | Shared |
---|---|---|
Debug | libscunitd.a | libscunitd.so |
Release | libscunit.a | libscunit.so |
This is hard to answer, since I don't know the exact structure or build systems you use in your project. However, these are the general steps you need to follow:
-
Build your preferred variant of SCUnit and place it in an appropriate directory along with the headers.
-
Include the main header
<SCUnit/scunit.h>
in your source files and start writing tests. You'll probably spend most of your time working with the contents of the<SCUnit/suite.h>
and<SCUnit/assert.h>
headers, which contain the main functionality needed to define suites, tests, setup and teardown functions, as well as assertions to be used in tests. -
Link SCUnit as a static or shared library when you build your test executable.
See the individual headers for detailed documentation on the interface and intended usage.
Besides the occasional learning experience, SCUnit gave me a chance to try out modern C23 in practice. I hadn't done any unit testing in a long time and needed a way to test some of my own side projects, including the last and current year of Advent of Code, which I plan on doing in C this time.
Of course, there are other great unit testing frameworks available for C, but they are often huge
or cumbersome to work with in my personal opinion, especially if tests and suites have to be
registered manually, or if a lot of configuration and setup code is required. Smaller frameworks,
ranging from tiny (essentially just providing a single assert
macro) to very powerful, often
didn't provide the exact set of features I was looking for, including style, consistent error
handling, and being written and intended for use in pure C.
I don't really expect any contributions, as SCUnit is more or less just one of my little side projects. However, if you find a bug or have a nice suggestion, feel free to open an issue and I'll see what I can do.
I originally got the idea to create SCUnit when I stumbled across this nice talk by Benno Rice, in which he presented some of the issues related to unit testing C code and two example frameworks one could use.
SCUnit was inspired by many other existing frameworks, including (but not limited to) Google Test, Check, Unity, Tau, µnit, tst and MinUnit.
SCUnit is licensed under the MIT License. See the LICENSE file for more information.