Quick Startο
Full Unit Test Exampleο
If you prefer learning through examples, take a look at the following repository:
Command Line Examplesο
Throughout the guide, you will find command line examples demonstrating how to use the git
command to add submodules to the project, create directories with mkdir
, and create or edit files with the text editor nano
. These examples are provided for illustration purposes and are not meant to imply that you must work exclusively in the command line to set up your project. We have found that most software developers can easily understand and follow these examples.
Formatting in Code Examplesο
To keep this guide short and easy to read, most comments and empty lines were removed from the code listings. This does not mean you should do the same for real world projects. Please follow the usual coding guidelines when formatting the code for unit tests and build files.
Recommended Project Structureο
Erbsland UnitTest is intended to be utilized as a GIT submodule within a larger project. To ensure seamless integration and easy usage, we recommend the following project structure for a project called erbsland-unittest-example
:
erbsland-unittest-example
βββ <libraries> # All dependent libraries for the `Example` project (submodules)
βββ erbsland-unittest # The Erbsland UnitTest library added as a submodule
βββ example-lib # The "Example" project to be tested by the unit test (added as submodule as well)
β βββ src (...)
β βββ CMakeLists.txt
βββ unittest # The actual unit test, containing one or more unit test binaries
β βββ src # The source files for the unit tests
β β βββ main.cpp # The main file for the executable
β β βββ BasicTest.hpp # Each set of tests has to be in a file ending in ...Test.hpp
β β βββ ContextTest.hpp # Each set contains a class with the same name of the file.
β β βββ LongTest.hpp
β βββ data # Data for unit tests
β βββ CMakeLists.txt
βββ CMakeLists.txt # The root project CMake file
By following this structure, you can ensure that your project is well-organized and easy to navigate. It also makes it simple to manage your unit tests, ensuring efficient and thorough testing of your libraries and applications.
Create the Project Structureο
cd ~
mkdir erbsland-unittest-example
cd erbsland-unittest-example
git init
git submodule add https://github.com/erbsland-dev/erbsland-unittest.git erbsland-unittest
git submodule init erbsland-unittest
Next, import the application or library and all dependencies as submodules to this project:
git submodule add https://github.com/erbsland-dev/erbsland-unittest-example-lib.git example-lib
git submodule init example-lib
Configuring the Root Project CMake Fileο
To set up the root project CMake file, use the following template:
nano CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(ExampleUnitTest)
add_subdirectory(erbsland-unittest)
add_subdirectory(example-lib)
add_subdirectory(unittest)
This configuration ensures that the project properly includes all necessary subdirectories for building and testing the erbsland-unittest-example
project.
Setting Up the Unit Test CMake Fileο
For a simple unit test with one executable, as illustrated in the example project structure, youβll need a CMake file like the one shown below:
mkdir unittest
cd unitest
nano CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(unittest)
add_executable(unittest
src/main.cpp)
target_compile_features(unittest PRIVATE cxx_std_17)
target_link_libraries(unittest PRIVATE erbsland-unittest-example-lib)
target_include_directories(unittest PRIVATE ../example-lib/src)
erbsland_unittest(TARGET unittest)
This configuration sets up the unittest
project with C++17 as the standard. It also adds the src
subdirectory and incorporates the Erbsland UnitTest system.
The last function call erbsland_unittest(TARGET unittest)
does most of the work for you. It configures your executable to link the unit test library and adds the correct include path. Also, it creates an additional target unittestMetaGen
that generates all the required additional code, so you donβt have to do it.
Writing the Testsο
Creating the main.cpp
Fileο
The main.cpp
file serves as the entry point for your unit tests. To create and set it up, follow the example below:
mkdir src
nano main.cpp
#include <erbsland/unittest/UnitTest.hpp>
ERBSLAND_UNITTEST_MAIN();
By using the ERBSLAND_UNITTEST_MAIN();
macro, you can efficiently create the main function for your unit test executable.
The main.cpp
file is purposefully added manually to the project. Automatically generating it can cause issues with build environments, as they often determine the compiler language based on the project files. Our unit testing system allows test classes to be declared in header files without the need for additional implementation files. Having the main.cpp file in place helps prevent many potential problems.
Creating Your First Unit Testο
To create unit tests using this system, follow these essential guidelines:
Each set of tests must be in a ``.hpp`` file or a combination of ``.hpp`` and ``.cpp`` files with a name ending in ``Test``.
The file must contain a ``class`` that shares the _same_ name as the file.
Tests should be _public_ methods within that class, with names beginning with lowercase ``test``.
Now, letβs create your first unit test. In most cases, using a single header file for all tests is more efficient, as the unit test system will automatically generate any missing implementation.
Begin by creating a test suite called Basic
. Name the file BasicTest.hpp
and assign the same name BasicTest
to the class inside the file:
nano BasicTest.hpp
#pragma once
#include <erbsland/unittest/UnitTest.hpp>
#include <ExampleLib.hpp>
using erbsland::ExampleLib;
class BasicTest : public el::UnitTest {
public:
void testMagic() {
auto exampleLib = ExampleLib{};
REQUIRE(exampleLib.getMagicWord() == "Magic");
}
}
Next add this file to the CMakeLists.txt
file:
cd ..
nano CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(unittest)
add_executable(unittest
src/BasicTest.hpp # <--- added the file
src/main.cpp)
target_compile_features(unittest PRIVATE cxx_std_17)
target_link_libraries(unittest PRIVATE erbsland-unittest-example-lib)
target_include_directories(unittest PRIVATE ../example-lib/src)
erbsland_unittest(TARGET unittest)
Create the Build Environment and Compile the Testο
In order to compile the test, you need to create the build files using CMake first:
cd ~
mkdir cmake-build
cd cmake-build
cmake ~/erbsland-unittest-example
If everything is ok, you will get an output like this:
-- The C compiler identification is (...)
-- The CXX compiler identification is (...)
-- Detecting C compiler ABI info
(...)
-- Found Python3: (...) (found suitable version "3.11.2", minimum required is "3.11") found components: Interpreter
-- Configuring done
-- Generating done
-- Build files have been written to: ~/erbsland-unittest-example
Next, compile the unit test:
make
(...)
[100%] Linking CXX executable unittest
[100%] Built target unittest
Run the Testο
Now you can run the test. It is placed in the subdirectory unittest of your build environment:
./unittest/unittest
If you followed along and just created a unit test with a single test function, you will get this output:
===[ Running 1 test suites with 1 tests ]===
Start Time: Tue Apr 25 10:41:23 2023
Filter: no filter set
Suite: Basic OK!
Test: Magic OK!
Total Test Duration: 8.63e-05 seconds
===[ SUCCESS | Successfully run all tests without errors. ]===
For the complete example you will get the following output, in colour if your console is supporting them:
===[ Running 2 test suites with 4 tests ]===
Start Time: Tue Apr 25 10:09:11 2023
Filter: no filter set
Suite: Basic OK!
Test: Magic OK!
Test: NameSetAndGet OK!
Test: IsNamePalindrome FAILED!
[1]: ~/erbsland-unittest-example/unittest/src/BasicTest.hpp:57: REQUIRE(exampleLib.isNamePalindrome())
Suite: Context OK!
Test: NameSetAndGet FAILED!
[2]: ~/erbsland-unittest-example/unittest/src/ContextTest.hpp:52: REQUIRE_FALSE(exampleLib.getNameLength() == expectedSize)
[1]: ~/erbsland-unittest-example/unittest/src/ContextTest.hpp:63: WITH_CONTEXT(setAndVerifyName_error("Lisa"))
ExampleLib:
getName() = "Lisa"
getNameLength() = 4
===[ ERROR SUMMARY ]===
Error 1 - Basic / IsNamePalindrome FAILED!
[1]: ~/erbsland-unittest-example/unittest/src/BasicTest.hpp:57: REQUIRE(exampleLib.isNamePalindrome())
Error 2 - Context / NameSetAndGet FAILED!
[2]: ~/erbsland-unittest-example/unittest/src/ContextTest.hpp:52: REQUIRE_FALSE(exampleLib.getNameLength() == expectedSize)
[1]: ~/erbsland-unittest-example/unittest/src/ContextTest.hpp:63: WITH_CONTEXT(setAndVerifyName_error("Lisa"))
===[ ERROR | 2 errors while running the tests. ]===
No worries about all the errors, the example library and unit test produce these intentionally do demonstrate the various features.
Get Command Line Helpο
To get a list with all command line options available for the test suite, use the -h
or --help
option:
./unittest/unittest --help
Erbsland Unit Test Help:
-h/--help ......... Display this help
-v/--verbose ...... Display verbose messages. Skipped tests.
-e ................ Stop at the first error.
-l/--list ......... List all suites and tests. Do not run any test.
-c/--no-color ..... Do not colorize the output and disable status updates.
-s/--no-summary ... Do not list the first three errors at the end of the run.
name:<name> ....... Exclusively run tests with the specified test or class name (case sensitive).
+name:<name> ...... Run tests with the specified test or class name, even optional ones.
-name:<name> ...... Skip tests with the specified test or class name.
target:<target> ... Exclusively run tests for the specified target.
+target:<target> .. Run tests with for the specified target, even optional ones.
-target:<target> .. Skip tests with for the specified target.
tag:<tag> ......... Exclusively run tests with the specified tags.
+tag:<tag> ........ Run tests with the specified tags, even optional ones.
-tag:<tag> ........ Skip tests with the specified tags.
By default all tests that are not marked with SKIP_BY_DEFAULT()
are enabled.
You can individually add +
or remove -
tests from this initial set.
If you specify one or more options like <opt>:<tag>
, only tests with
are enabled, and further +/-
options will change this set.
The processing order of the options is <opt>
, +<opt>
, -<opt>
and does not depend
on the order how they are specified on the command line. Therefore -
always have
the highest priority and will skip these tests no mather what was specified otherwise.
Filtering Tests, Select the Tests you Like to Runο
If you look at this list, you see that the test IsNamePalindromeBruteForce
is skipped by default and the tag long-test
indicates why. So, lets start only the test with the tag long-test
:
./unittest/unittest tag:long-test
===[ Running 1 test suites with 1 tests ]===
Start Time: Tue Apr 25 10:48:11 2023
Filter:
Tags: only use 'long-test'
Suite: Long OK!
Testing name 0: baaaaa
Testing name 1000000: phxeca
Testing name 2000000: dpujea
Testing name 3000000: rwroga
(...)
Testing name 293000000: vylery
Testing name 294000000: jgjjty
Testing name 295000000: xngovy
Testing name 296000000: lvdtxy
Testing name 297000000: zcbyzy
Test: IsNamePalindromeBruteForce OK!
Total Test Duration: 85.1 seconds
===[ SUCCESS | Successfully run all tests without errors. ]===
This is indeed a long-running test (only when compiled for debugging).
Whatβs Next?ο
If you not already did, have a look at the full example that demonstrates most of the basic features of the unit test.
π UnitTest Example π
For more details about the unit testing system, have a look into the documentation:
π Documentation π