I’m starting a new project in C++, but I’ve run into a couple of questions before starting:

  1. Which build system should I use?

  2. Which unit testing framework?

Tip
If you just want project template so you can have a C++ project skeleton ready in seconds, just go to https://github.com/carlosvin/uuid-cpp and follow the instructions in README.md.

Choosing Build System (Meson)

I have used before Make, Maven, Scons, Gradle and Autotools, but I have some reasons to try find something else:

Autotools

It is not easy to configure and maintain. There are several configuration files and several configuration steps.

Gradle

C feature is still incubating. Not very fast. You can check a similar example project at link:/posts/gradle-cpp/[Build C project with Gradle].

Make

I don’t love the syntax. Files tends to get messy as project grows.

Scons

It is just slower and not as easy to understand than Meson.

Maven

It is slow and you might end up "Javatizing" your C++ project structure.

Note
I’ve listed just things I don’t like, those projects have other great features.

CMake vs Meson

After discarding previous ones, I’m considering Meson or CMake. Both are fast build systems:

Although Meson is written in Python, it generates a Ninja build project. First time you configure the project you have to run Meson, but for building or testing you are actually running Ninja.

CMake is also able to generate Ninja files among other formats, check CMake generators documentation for more information.

CMake

It has a big advantage over Meson, it is mature and widely used in many projects, which means there are many examples and it will fulfill your C++ project building needs.

Meson

It is a young project compared with CMake, but it is growing quite fast and it has been adopted in other big projects like Gnome, they have an initiative to port from Autotools to Meson.

Finally I’ve chosen Meson because syntax is really clear to me, when I read meson.build file I can quickly understand what is happening during build process.

Steps to compile and test a project
$ meson build .  (1)
$ cd build
$ ninja build    (2)
$ ninja test     (3)
  1. First time you configure the project

  2. Each time you build it

  3. Each time you run tests

Other build systems comparisons

I’ve found two interesting comparisons about available C++ build systems, they might be a little be biased because those comparisons come from Meson and Scons.

Unit Testing Framework

I have used some xUnit based libraries like UnitTest++, CppUTest or Google Test which match perfectly with Google Mock. If you want a safe bet that fulfills almost of your testing needs I highly recommend Google Test.

But time ago I found a testing framework with some interesting features, Catch:

  • It is just a header file with no external dependencies, so very easy to start (wget + include downloaded file).

  • You can use normal unit test style or BDD-style

If you want to know more about Catch, I recommend you to give it a try, it is a matter of 2 minutes to have a simple example up and running. You can also read some interesting articles like Why do we need yet another C test framework?] or https://blog.coldflake.com/posts/Testing-C-with-a-new-Catch/[Testing C++ With A New Catch.

doctest: A Catch alternative

There is another testing framework named doctest, with same benefits as Catch, but it promises to be faster and lighter (performance results) than Catch.

doctest is modeled after Catch and some parts of the code have been taken directly, but there are differences.

It hasn’t been easy to decide, both are really similar, following you can see differences:

diff between doctest and catch branches
@@ -1,12 +1,12 @@
-#define CATCH_CONFIG_MAIN // It tells Catch to provide a main() - only do this in one cpp file
+#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN

-#include "catch.hpp"
+#include "doctest.h"
#include "Uuid.h"
#include <string>

constexpr int MAX_ITERS = 100;

-TEST_CASE("Uuid", "[uuid]")
+TEST_CASE("Uuid")
{
for (int i = 0; i < MAX_ITERS; i++)
{
@@ -26,7 +26,7 @@ TEST_CASE("Uuid", "[uuid]")

// BDD style

-SCENARIO("UUID creation", "[Uuid]")
+SCENARIO("UUID creation")
{

GIVEN("A random UUID ")

I’ve finally chosen doctest because it promises to be faster: performance results.

Note
I’ve created project using both frameworks you can find them in corresponding branches: doctest branch or catch branch.

Example

I’ve created an example to illustrate this article: https://github.com/carlosvin/uuid-cpp.

It is a basic implementation of UUID pseudo-random generator based on mt19937 which is not cryptographically secure.

Project output artifacts

When we install the project using Meson (Ninja), we will get some artifacts generated and copied in our system.

  • Shared library: libuuid.

  • Header library for developers who want to use the shared library: include/Uuid.h.

  • Executable uuidgen (UUID generator).

  • Test executable (not installed). It tests shared library.

For example, if you execute ninja install on Linux, you will get something like:

/usr/local/lib/libuuid.so
/usr/local/include/Uuid.h
/usr/local/bin/uuidgen

Project structure (Fork project)

meson.build

Root project file configuration. It defines project properties and subdirectories.

meson.build
project(
    'cpp-meson-example', # project name
    'cpp', # C++ project, e.g: for C project
    version : '1.0.0',
    license : 'MIT',
    default_options : ['cpp_std=c++11']) # compile for C++

# it will be referred from subdir projects
inc = include_directories('include')

# meson will try to find a meson.build file inside following directories
subdir('include')
subdir('src')
subdir('test')
include
meson.build

Build configuration file for include directory.

include/meson.build
# Select header files to be installed
install_headers('Uuid.h')
Uuid.h

Header file, it is the library interface definition which will be included from projects using that library

include/Uuid.h
namespace ids {

class Uuid {
    private:
    // ...
src
meson.build (src)

It declares 2 output artifacts, library libuuid and executable uuidgen. Executable depends on the libary, it will use the libary to generate UUID.

src/meson.build
libuuid = shared_library(
    'uuid', (1)
    'Uuid.cpp', (2)
    include_directories : inc, (3)
    install : true) (4)

uuidgen = executable(
    'uuidgen', (5)
    'main.cpp', (6)
    include_directories : inc, (7)
    link_with : libuuid, (8)
    install : true) (9)
  1. library name

  2. source files to be compile

  3. previously declared include directories in root meson.build

  4. libuuid will be part of project installation

  5. executable name

  6. source files to compile

  7. previously declared include directories in root meson.build

  8. linking executable with shared previously declared shared library libuuid

  9. uuidgen executable be part of project installation

    main.cpp

    Entry point for main executable uuidgen.

src/main.cpp
#include "Uuid.h"
#include <iostream>

int main()
{
    ids::Uuid uuid;
    std::cout << uuid.to_str() << std::endl;
    return 0;
}
Uuid.cpp

Implementation of declared class in header file.

src/Uuid.cpp
#include "Uuid.h"

Uuid::Uuid()
{ // ...
test
meson.build (test)

File to configure tests build process.

test/meson.build
testexe = executable(
    'testexe', (1)
    'uuid_test.cpp', (2)
    include_directories : inc, (3)
    link_with : libuuid) (4)

test('Uuid test', testexe) (5)

test('Uuid test with args and env', testexe, args : ['arg1', 'arg2'], env : ['FOO=bar']) (6)
  1. test executable name

  2. tests source files to be compiled

  3. declared include directories in root meson.build

  4. link test executable with previously declared shared library libuuid

  5. test execution

  6. we can specify other test execution passing arguments or environment variables

    doctest.h

    doctest library in a single header file. You can try to automate library installation as part of your build process, but I haven not figured out yet a way to do it with Meson. For now, I have installed it manually:

Add doctest to your project
cd test
wget https://raw.githubusercontent.com/onqtam/doctest/master/doctest/doctest.h
uuid_test.cpp

Tests implementation.

test/uuid_test.cpp
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN

#include "doctest.h"
#include "Uuid.h"
#include <string>

constexpr int MAX_ITERS = 100;

TEST_CASE("Uuid")
{
    for (int i = 0; i < MAX_ITERS; i++)
    {
        ids::Uuid uuid;
        std::string uuid_str{uuid.to_str()};

        INFO(uuid_str);

        // If assertion fails test execution is stopped
        REQUIRE(uuid_str.size() == 36);

        // If assertion fails test execution continues
        CHECK(uuid.most > 0);
        CHECK(uuid.least > 0);
    }
}

// BDD style

SCENARIO("UUID creation")
{

    GIVEN("A random UUID ")
    {

        ids::Uuid uuid;
        std::string uuid_str{uuid.to_str()};

        REQUIRE(uuid_str.size() == 36);

        WHEN("get the most and least")
        {
            THEN("should be more than 0")
            {
                CHECK(uuid.most > 0);
                CHECK(uuid.least > 0);
            }
        }
    }
}
Tip
You can find how to build and test the example project at: https://github.com/carlosvin/uuid-cpp#how-to-build-the-example