Skip to content

Writing your first test

In this example, we will create a simple calculator in C++ using Cest to test its behavior. To make things easy, we will run the build commands directly in the terminal, without using a build system. In the next tutorial, we will see how to integrate Cest with CMake.

Setup

First, let's create an empty directory where we will keep all the project's files. Then, the subdirectories to keep the source and build results of the project.

Create all the directories
mkdir calculator
cd calculator
mkdir src
mkdir test
mkdir lib
mkdir build

The resulting directory structure will be like the following:

Directory structure
/calculator
├ /src
├ /test
├ /lib
└ /build

Now, let's create the application's source files. Let's start by creating a main.cpp file to hold the application's entrypoint, and a calculator.cpp file to hold the application's business logic.

We will start by performing the sum of 2+2, but let's keep the implementation empty first. We will fill in the details after writing its test. That will ensure the calculation is correct.

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

int main(int argc, char *argv[]) {
  std::cout << "2+2 = " << Sum(2, 2) << std::endl;
  return 0;
}
src/calculator.cpp
int Sum(int a, int b) {
  return 0;
}
src/calculator.h
#pragma once

int Sum(int a, int b);

After creating these files, the directory structure will look like this:

Directory structure
/calculator
├ /src
│ ├ main.cpp
│ ├ calculator.cpp
│ └ calculator.h
├ /test
├ /lib
└ /build

Finally, let's install the latest version of Cest Framework. You can get the latest version from GitHub, or directly run in the terminal:

Installing Cest Framework
cd lib
wget https://github.com/cegonse/cest/releases/download/v2/cest
cd ..

After these commands finish, we will have the full setup in place:

Directory structure
/calculator
├ /src
│ ├ main.cpp
│ ├ calculator.cpp
│ └ calculator.h
├ /test
├ /lib
│ └ cest
└ /build

Building the application

To build the application, let's invoke the C++ compiler and build all the project's source files into a single binary. We will assume the GNU Compiler is installed, invoking g++. If you are using macOS and have installed the XCode Build Tools, you will probably have CLang installed instead. In that case, run clang++.

Building the application
g++ src/main.cpp src/calculator.cpp -o build/calculator

If something went wrong during the compilation process, go to the previous step and check whether you are missing any file or you have any errors in the source files. Otherwise, the calculator program will be placed inside the /build directory:

Directory structure
/calculator
├ /src
│ ├ main.cpp
│ ├ calculator.cpp
│ └ calculator.h
├ /test
├ /lib
│ └ cest
└ /build
  └ calculator

Running the application will show the calculation result, which is wrong:

Running the application
./build/calculator
2+2 = 0

Writing our first test

Now we have a calculator application that builds and runs, but the business logic is not implemented yet. Let's build its logic through a test, ensuring the behavior is correct.

First, let's create the test file for the calculator.cpp file. Check the annotations () to know more about each section of the test file.

test/calculator.test.cpp
#include <cest>
#include <calculator.h>

describe("Calculator", []() { // (1)
  describe("sum()", []() { // (2)
    it("adds two numbers", []() {
      auto a = 2, b = 2;
      auto r = Sum(a, b);
      expect(r).toEqual(4); // (3)
    });

    it("supports negative numbers", []() {
      auto a = -2, b = 4;
      auto r = Sum(a, b);
      expect(r).toEqual(2);
    });
  });
});
  1. describe blocks define test-suites. Each test suite may have any number of tests.
  2. Test-suites can be nested one inside each other. Cest will run them in outside-in order.
  3. expect() is the keyword that lets you do assertions on the calculation results of your code. The whole set of available assertions is available in the API reference

With our test in place, let's build and run the calculator test. The test can be easily run by executing the compiled file, as Cest tests are stand-alone executable applications. Check out the different output styles in the API reference API reference or by running the test with the --help flag.

Compiling the test
g++ -Ilib -Isrc src/calculator.cpp test/calculator.test.cpp -o build/calculator_test

After compiling the test, the directory structure will look like this:

Directory structure
/calculator
├ /src
│ ├ main.cpp
│ ├ calculator.cpp
│ └ calculator.h
├ /test
│ └ calculator.test.cpp
├ /lib
│ └ cest
└ /build
  ├ calculator_test
  └ calculator

Finally, let's run the test and see its output. The output may look bare in this snippet, but you will see results with full color and rich text in the terminal.

Running the test
./build/calculator_test
 FAIL  test/calculator.test.cpp:6 adds two numbers
 Failed at line 9: Expected 4, was 0
 FAIL  test/calculator.test.cpp:12 supports negative numbers
 Failed at line 15: Expected 2, was 0

We can see both tests have failed, as expected. Let's correct the implementation of our business logic to ensure the tests pass:

src/calculator.cpp
int Sum(int a, int b) {
  return a + b;
}

And now, when running the tests:

Building and running the test again
g++ -Ilib -Isrc src/calculator.cpp test/calculator.test.cpp -o build/calculator_test
./build/calculator_test
 PASS  test/calculator.test.cpp:6 adds two numbers
 PASS  test/calculator.test.cpp:12 supports negative numbers

All tests have passed! If we try compiling and running the application again, we will now see the correct result on the terminal:

Building and running the application
g++ src/main.cpp src/calculator.cpp -o build/calculator
./build/calculator
2+2 = 4

Next reading

With this simple example, we have seen how does Cest Framework work, how to create a simple test, and how to integrate it with an application.

Next, we will learn how to integrate Cest Runner to easily manage our test suite.