Ut
C++20 μ(micro)/Unit Testing framework
Install / Use
/learn @boost-ext/UtREADME
<a href="https://conan.io/center/boost-ext-ut" target="_blank"></a>
<a href="https://github.com/boost-ext/ut/actions/workflows/linux.yml" target="_blank">
</a>
<a href="https://github.com/boost-ext/ut/actions/workflows/macos.yml" target="_blank">
</a>
<a href="https://github.com/boost-ext/ut/actions/workflows/windows.yml" target="_blank">
</a>
<a href="https://codecov.io/gh/boost-ext/ut" target="_blank">
</a>
<a href="https://godbolt.org/z/f4jEcv9vo">
</a>
<a href="https://aur.archlinux.org/packages/ut/">
</a>
"If you liked it then you
"should have put a"_teston it", Beyonce rule
UT / μt
| Motivation | Quick Start | Overview | Tutorial | Examples | User Guide | FAQ | Benchmarks |
<details open><summary>C++ <b>single header/single module, macro-free</b> μ(micro)/Unit Testing Framework</summary> <p>#include <boost/ut.hpp> // import boost.ut;
constexpr auto sum(auto... values) { return (values + ...); }
int main() {
using namespace boost::ut;
"sum"_test = [] {
expect(sum(0) == 0_i);
expect(sum(1, 2) == 3_i);
expect(sum(1, 2) > 0_i and 41_i == sum(40, 2)); // row 11
};
}
UT starts =====================================================================
Running test "sum"...
FAILED in: ...\example.cpp:11 - test condition: [(3 > 0 and 41 == 42)]
===============================================================================
Suite global
tests: 1 | 1 failed
asserts: 3 | 2 passed | 1 failed
Completed =====================================================================
https://godbolt.org/z/T873jdEx4
<a name="motivation"></a>
<details open><summary>Motivation</summary> <p>Testing is a very important part of the Software Development, however, C++ doesn't provide any good testing facilities out of the box, which often leads into a poor testing experience for develops and/or lack of tests/coverage in general.
One should treat testing code as production code!
Additionally, well established testing practises such as Test Driven Development (TDD)/Behaviour Driven Development (BDD) are often not followed due to the same reasons.
The following snippet is a common example of testing with projects in C++.
int main() {
// should sum numbers
{
assert(3 == sum(1, 2));
}
}
There are quite a few problems with the approach above
- No names for tests (Hard to follow intentions by further readers)
- No automatic registration of tests (No way to run specific tests)
- Hard to debug (Assertions don't provide any information why it failed)
- Hard to scale (No easy path forward for parameterized tests, multiple suites, parallel execution, etc...)
- Hard to integrate (No easy way to have a custom output such as XML for CI integration)
- Easy to make mistakes (With implicit casting, floating point comparison, pointer comparison for strings, etc...)
- Hard to follow good practises such as
TDD/BDD(Lack of support for sections and declarative expressions) - ...
UT is trying to address these issues by simplifying testing experience with a few simple steps:
- Just get a single header or module+header
- Integrate it into your project
- Learn a few simple concepts (expect, test, suite)
And you good to go!
Okay, great, but why I would use UT over other/similar testing frameworks already available in C++?
Great question! There are a few unique features which makes UT worth trying
- Firstly, it supports all the basic Unit Testing Framework features (automatic registration of tests, assertions, suites, etc...)
- It's easy to integrate (it's just one header/module)
- It's macro free which makes testing experience that much nicer (it uses modern C++ features instead, macros are opt-in rather than being compulsory - Can I still use macros?)
- It's flexible (all parts of the framework such as: runner, reporter, printer can be customized, basically most other Unit Testing Frameworks can be implemented on top of UT primitives)
- It has smaller learning curve (just a few simple concepts (expect, test, suite))
- It leverages C++ features to support more complex testing (parameterized)
- It's faster to compile and execute than similar frameworks which makes it suitable for bigger projects without additional hassle (Benchmarks)
- It supports TDD/BDD workflows
- It supports Gherkin specification
- It supports Spec
- ...
Sounds intriguing/interesting? Learn more at
</p> </details><a name="quick-start"></a>
<details open><summary>Quick Start</summary> <p></p> </details>https://bit.ly/ut-quick-start (slides)
<a name="overview"></a>
<details open><summary>Overview</summary> <p>- No dependencies (C++20, Tested Compilers: GCC-9+, Clang-9.0+, Apple Clang-11.0.0+, MSVC-2019+*, Clang-cl-9.0+
- Single header/module (boost/ut.hpp)
- Macro-free (How does it work?)
- Easy to use (Minimal API -
test, suite, operators, literals, [expect]) - Fast to compile/execute (Benchmarks)
- Features (Assertions, Suites, Tests, Sections, Parameterized, BDD, Gherkin, Spec, Matchers, Logging, Runners, Reporters, ...)
- Integrations (ApprovalTests.cpp)
<a name="tutorial"></a>
<details open><summary>Tutorial</summary> <p> <details open><summary> Step 0: Get it...</summary> <p>Get the latest latest header/module from here!
Include/Import
// #include <boost/ut.hpp> // single header
// import boost.ut; // single module (C++20)
int main() { }
Compile & Run
$CXX main.cpp && ./a.out
All tests passed (0 assert in 0 test)
[Optional] Install it
cmake -Bbuild -H.
cd build && make # run tests
cd build && make install # install
[Optional] CMake integration
This project provides a CMake config and target.
Just load ut with find_package to import the Boost::ut target.
Linking against this target will add the necessary include directory for the single header file.
This is demonstrated in the following example.
find_package(ut REQUIRED)
add_library(my_test my_test.cpp)
target_link_libraries(my_test PRIVATE Boost::ut)
[Optional] Conan integration
The boost-ext-ut package is available from Conan Center.
Just include it in your project's Conanfile with boost-ext-ut/2.3.1.
Let's write our first assertion, shall we?
#include <boost/ut.hpp>
int main() {
boost::ut::expect(true);
}
UT starts =====================================================================
Suite 'global': all tests passed (1 asserts in 0 tests)
Completed =====================================================================
https://godbolt.org/z/8MffxExsP
Okay, let's make it fail now?
#include <boost/ut.hpp>
int main() {
boost::ut::expect(1 == 2); // row 4
}
.... v vvvvv
FAILED in: ...\example.cpp:4 - test condition: [false]
===============================================================================
Suite global
tests: 0 | 0 failed
asserts: 1 | 0 passed | 1 failed
Completed =====================================================================
https://godbolt.org/z/8GjGoc7cn
Notice that expression
1 == 2hasn't been printed. Instead we gotfalse?
Let's print it then?
#include <boost/ut.hpp>
int main() {
using namespace boost::ut;
expect(1_i == 2); // r
