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)](https://en.wikipedia.org/wiki/Test-driven_development)/[Behaviour Driven Development (BDD)](https://en.wikipedia.org/wiki/Behavior-driven_development) are often not followed due to the same reasons. The following snippet is a common example of testing with projects in C++. ```cpp 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/module](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp) * Integrate it into your project * Learn a few simple concepts ([expect, test, suite](#api)) And you good to go! Okay, great, but why I would use `UT` over other/similar testing frameworks already available in C++? * [Boost.Test](https://github.com/boostorg/test) * [GoogleTest](https://github.com/google/googletest) * [Catch](https://github.com/catchorg/Catch2) * [...](https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#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](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp)) * 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?](#macros)) * It's flexible (all parts of the framework such as: [runner, reporter, printer](#examples) 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](#api))) * It leverages C++ features to support more complex testing ([parameterized](#examples)) * It's faster to compile and execute than similar frameworks which makes it suitable for bigger projects without additional hassle ([Benchmarks](#benchmarks)) * It supports [TDD/BDD](#examples) workflows * It supports [Gherkin](#examples) specification * It supports [Spec](#examples) * ... Sounds intriguing/interesting? Learn more at * [Tutorial](#tutorial) * [Examples](#examples) * [User-Guide](#user-guide)
> https://bit.ly/ut-quick-start (slides)
* No dependencies ([C++20](#cpp-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](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp)) * Macro-free ([How does it work?](#how-it-works)) * Easy to use ([Minimal API](#api) - `test, suite, operators, literals, [expect]`) * Fast to compile/execute ([Benchmarks](#benchmarks)) * Features ([Assertions](https://github.com/boost-ext/ut/blob/master/example/expect.cpp), [Suites](https://github.com/boost-ext/ut/blob/master/example/suite.cpp), [Tests](https://github.com/boost-ext/ut/blob/master/example/test.cpp), [Sections](https://github.com/boost-ext/ut/blob/master/example/section.cpp), [Parameterized](https://github.com/boost-ext/ut/blob/master/example/parameterized.cpp), [BDD](https://github.com/boost-ext/ut/blob/master/example/BDD.cpp), [Gherkin](https://github.com/boost-ext/ut/blob/master/example/gherkin.cpp), [Spec](https://github.com/boost-ext/ut/blob/master/example/spec.cpp), [Matchers](https://github.com/boost-ext/ut/blob/master/example/matcher.cpp), [Logging](https://github.com/boost-ext/ut/blob/master/example/log.cpp), [Runners](https://github.com/boost-ext/ut/blob/master/example/cfg/runner.cpp), [Reporters](https://github.com/boost-ext/ut/blob/master/example/cfg/reporter.cpp), [...](https://github.com/boost-ext/ut/blob/master/example)) * Integrations ([ApprovalTests.cpp](https://github.com/approvals/ApprovalTests.cpp/releases/tag/v.7.0.0))
> Get the latest latest header/module from [here!](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp)
> Include/Import
```cpp
// #include
> Let's write our first assertion, shall we?
```cpp
int main() {
boost::ut::expect(true);
}
```
```
All tests passed (1 asserts in 0 test)
```
> https://godbolt.org/z/vfx-eB
> Okay, let's make it fail now?
```cpp
int main() {
boost::ut::expect(1 == 2);
}
```
```
main.cpp:4:FAILED [false]
===============================================================================
tests: 0 | 0 failed
asserts: 1 | 0 passed | 1 failed
```
> https://godbolt.org/z/7qTePx
> Notice that expression `1 == 2` hasn't been printed. Instead we got `false`?
> Let's print it then?
```cpp
int main() {
using namespace boost::ut;
expect(1_i == 2);
}
```
```
main.cpp:4:FAILED [1 == 2]
===============================================================================
tests: 0 | 0 failed
asserts: 1 | 0 passed | 1 failed
```
> https://godbolt.org/z/7MXVzu
> Okay, now we have it! `1 == 2` has been printed as expected.
> Notice the User Defined Literal (UDL) `1_i` was used.
> `_i` is a compile-time constant integer value
* It allows to override comparison operators ð
* It disallow comparison of different types ð
See the [User-guide](#user-guide) for more details.
> Alternatively, a `terse` notation (no expect required) can be used.
```cpp
int main() {
using namespace boost::ut::literals;
using namespace boost::ut::operators::terse;
1_i == 2; // terse notation
}
```
```
main.cpp:7:FAILED [1 == 2]
===============================================================================
tests: 0 | 0 failed
asserts: 1 | 0 passed | 1 failed
```
> https://godbolt.org/z/s77GSm
> Other expression syntaxes are also available.
```cpp
expect(1_i == 2); // UDL syntax
expect(1 == 2_i); // UDL syntax
expect(that % 1 == 2); // Matcher syntax
expect(eq(1, 2)); // eq/neq/gt/ge/lt/le
```
```
main.cpp:6:FAILED [1 == 2]
main.cpp:7:FAILED [1 == 2]
main.cpp:8:FAILED [1 == 2]
main.cpp:9:FAILED [1 == 2]
===============================================================================
tests: 0 | 0 failed
asserts: 4 | 0 passed | 4 failed
```
> https://godbolt.org/z/QbgGtc
> Okay, but what about the case if my assertion is fatal.
> Meaning that the program will crash unless the processing will be terminated.
> Nothing easier, let's just add `>> fatal` after the expected expression to make it fatal.
```cpp
expect((1 == 2_i) >> fatal); // fatal assertion
expect(1_i == 2); // not executed
```
```
main.cpp:6:FAILED [1 == 2]
===============================================================================
tests: 1 | 1 failed
asserts: 2 | 0 passed | 2 failed
```
> https://godbolt.org/z/WMe8Y1
> But my expression is more complex than just simple comparisons.
> Not a problem, logic operators are also supported in the `expect` ð.
```cpp
expect(42l == 42_l and 1 == 2_i); // compound expression
```
```
main.cpp:5:FAILED [(42 == 42 and 1 == 2)]
===============================================================================
tests: 0 | 0 failed
asserts: 1 | 0 passed | 1 failed
```
> https://godbolt.org/z/aEhX4t
> Can I add a custom message though?
> Sure, `expect` calls are streamable!
```cpp
int main() {
expect(42l == 42_l and 1 == 2_i) << "additional info";
}
```
```
main.cpp:5:FAILED [(42 == 42 and 1 == 2)] additional info
===============================================================================
tests: 0 | 0 failed
asserts: 1 | 0 passed | 1 failed
```
> https://godbolt.org/z/v2PDuU
> Assertions are great, but how to combine them into more cohesive units?
> `Test cases` are the way to go! They allow to group expectations for the same functionality into coherent units.
```cpp
"hello world"_test = [] { };
```
> Alternatively `test("hello world") = [] {}` can be used.
```
All tests passed (0 asserts in 1 tests)
```
> https://godbolt.org/z/Bh-EmY
> Notice `1 tests` but `0 asserts`.
> Let's make our first end-2-end test case, shall we?
```cpp
int main() {
"hello world"_test = [] {
int i = 43;
expect(42_i == i);
};
}
```
```
Running "hello world"...
main.cpp:8:FAILED [42 == 43]
FAILED
===============================================================================
tests: 1 | 1 failed
asserts: 1 | 0 passed | 1 failed
```
> https://godbolt.org/z/Y43mXz
> ð We are done here!
> I'd like to nest my tests, though and share setup/tear-down.
> With lambdas used to represents `tests/sections` we can easily achieve that.
> Let's just take a look at the following example.
```cpp
int main() {
"[vector]"_test = [] {
std::vector
> Okay, but my project is more complex than that. How can I scale?
> `Test suites` will make that possible. By using `suite` in translation units
> `tests` defined inside will be automatically registered ð
```cpp
suite errors = [] {
"exception"_test = [] {
expect(throws([] { throw 0; })) << "throws any exception";
};
"failure"_test = [] {
expect(aborts([] { assert(false); }));
};
};
int main() { }
```
```
All tests passed (2 asserts in 2 tests)
```
> https://godbolt.org/z/_ccGwZ
---
> What's next?
> * [Examples](#examples)
> * [User-Guide](#user-guide)
Step 0: Get it...
Step 1: Expect it...
Step 2: Group it...
Step 3: Scale it...
```cpp
// operators
expect(0_i == sum());
expect(2_i != sum(1, 2));
expect(sum(1) >= 0_i);
expect(sum(1) <= 1_i);
```
```cpp
// message
expect(3_i == sum(1, 2)) << "wrong sum";
```
```cpp
// expressions
expect(0_i == sum() and 42_i == sum(40, 2));
expect(0_i == sum() or 1_i == sum()) << "compound";
```
```cpp
// matchers
expect(that % 0 == sum());
expect(that % 42 == sum(40, 2) and that % (1 + 2) == sum(1, 2));
expect(that % 1 != 2 or 2_i > 3);
```
```cpp
// eq/neq/gt/ge/lt/le
expect(eq(42, sum(40, 2)));
expect(neq(1, 2));
expect(eq(sum(1), 1) and neq(sum(1, 2), 2));
expect(eq(1, 1) and that % 1 == 1 and 1_i == 1);
```
```cpp
// floating points
expect(42.1_d == 42.101) << "epsilon=0.1";
expect(42.10_d == 42.101) << "epsilon=0.01";
expect(42.10000001 == 42.1_d) << "epsilon=0.1";
```
```cpp
// constant
constexpr auto compile_time_v = 42;
auto run_time_v = 99;
expect(constant<42_i == compile_time_v> and run_time_v == 99_i);
```
```cpp
// failure
expect(1_i == 2) << "should fail";
expect(sum() == 1_i or 2_i == sum()) << "sum?";
```
```
assertions.cpp:53:FAILED [1 == 2] should fail
assertions.cpp:54:FAILED [(0 == 1 or 2 == 0)] sum?
===============================================================================
tests: 0 | 0 failed
asserts: 20 | 18 passed | 2 failed
```
> https://godbolt.org/z/E1c7G5
```cpp
"run UDL"_test = [] {
expect(42_i == 42);
};
skip / "don't run UDL"_test = [] {
expect(42_i == 43) << "should not fire!";
};
```
```
All tests passed (1 asserts in 1 tests)
1 tests skipped
```
```cpp
test("run function") = [] {
expect(42_i == 42);
};
skip / test("don't run function") = [] {
expect(42_i == 43) << "should not fire!";
};
```
```
All tests passed (1 asserts in 1 tests)
1 tests skipped
```
```cpp
tag("nightly") / tag("slow") /
"performance"_test= [] {
expect(42_i == 42);
};
tag("slow") /
"run slowly"_test= [] {
expect(42_i == 43) << "should not fire!";
};
```
```
cfg
```cpp
"[vector]"_test = [] {
std::vector
```cpp
"Scenario"_test = [] {
given("I have...") = [] {
when("I run...") = [] {
then("I expect...") = [] { expect(1_i == 1); };
then("I expect...") = [] { expect(1 == 1_i); };
};
};
};
```
```
All tests passed (2 asserts in 1 tests)
```
> https://godbolt.org/z/mNBySr
```cpp
int main() {
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("*") = [&] {
steps.scenario("*") = [&] {
steps.given("I have a number {value}") = [&](int value) {
auto number = value;
steps.when("I add {value} to it") = [&](int value) {
number += value;
};
steps.then("I expect number to be {value}") = [&](int value) {
expect(that % number == value);
};
};
};
};
};
"Gherkin"_test = steps |
R"(
Feature: Number
Scenario: Addition
Given I have a number 40
When I add 2 to it
Then I expect number to be 42
)";
}
```
```
All tests passed (1 asserts in 1 tests)
```
> https://godbolt.org/z/BP3hyt
```cpp
int main() {
describe("equality") = [] {
it("should be equal") = [] { expect(0_i == 0); };
it("should not be equal") = [] { expect(1_i != 0); };
};
}
```
```
All tests passed (2 asserts in 1 tests)
```
> https://godbolt.org/z/BXYJ3a
```cpp
for (auto i : std::vector{1, 2, 3}) {
test("parameterized " + std::to_string(i)) = [i] {
expect(that % i > 0);
};
}
"args"_test =
[](auto arg) {
expect(arg >= 1_i);
}
| std::vector{1, 2, 3};
"types"_test =
[]
```cpp
namespace ut = boost::ut;
ut::suite errors = [] {
using namespace ut;
"throws"_test = [] {
expect(throws([] { throw 0; }));
};
"doesn't throw"_test = [] {
expect(nothrow([]{}));
};
};
int main() { }
```
```
All tests passed (2 asserts in 2 tests)
```
> https://godbolt.org/z/CFbTP9
```cpp
"logging"_test = [] {
log << "pre";
expect(42_i == 43) << "message on failure";
log << "post";
};
```
```
Running "logging"...
pre
logging.cpp:8:FAILED [42 == 43] message on failure
post
FAILED
===============================================================================
tests: 1 | 1 failed
asserts: 1 | 0 passed | 1 failed
```
> https://godbolt.org/z/26fPSY
```cpp
"matchers"_test = [] {
constexpr auto is_between = [](auto lhs, auto rhs) {
return [=](auto value) {
return that % value >= lhs and that % value <= rhs;
};
};
expect(is_between(1, 100)(42));
expect(not is_between(1, 100)(0));
};
```
```
All tests passed (2 asserts in 1 tests)
```
> https://godbolt.org/z/4qwrCi
```cpp
"exceptions/aborts"_test = [] {
expect(throws
```cpp
namespace ut = boost::ut;
namespace cfg {
class runner {
public:
template
```cpp
namespace ut = boost::ut;
namespace cfg {
class reporter {
public:
auto on(ut::events::test_begin) -> void {}
auto on(ut::events::test_run) -> void {}
auto on(ut::events::test_skip) -> void {}
auto on(ut::events::test_end) -> void {}
template
```cpp
namespace ut = boost::ut;
namespace cfg {
struct printer : ut::printer {
template Assertions
Tests
Run/Skip/Tag
Sections
Behavior Driven Development (BDD)
Gherkin
Spec
Parameterized
Suites
Misc
Logging
Matchers
Exceptions/Aborts
Config
Runner
Reporter
Printer
```cpp
export module boost.ut; /// __cpp_modules
namespace boost::inline ext::ut::inline v1_1_9 {
/**
* Represents test suite object
*/
struct suite final {
/**
* Creates and executes test suite
* @example suite _ = [] {};
* @param suite test suite function
*/
constexpr explicit(false) suite(auto suite);
};
/**
* Creates a test
* @example "name"_test = [] {};
* @return test object to be executed
*/
constexpr auto operator""_test;
/**
* Creates a test
* @example test("name") = [] {};
* @return test object to be executed
*/
constexpr auto test = [](const auto name);
/**
* Creates a test
* @example should("name") = [] {};
* @return test object to be executed
*/
constexpr auto should = [](const auto name);
/**
* Behaviour Driven Development (BDD) helper functions
* @param name step name
* @return test object to be executed
*/
constexpr auto given = [](const auto name);
constexpr auto when = [](const auto name);
constexpr auto then = [](const auto name);
/**
* Evaluates an expression
* @example expect(42 == 42_i and 1 != 2_i);
* @param expr expression to be evaluated
* @param source location https://en.cppreference.com/w/cpp/utility/source_location
* @return stream
*/
constexpr OStream& expect(
Expression expr,
const std::source_location& location = std::source_location::current()
);
struct {
/**
* @example (that % 42 == 42);
* @param expr expression to be evaluated
*/
[[nodiscard]] constexpr auto operator%(Expression expr) const;
} that{};
inline namespace literals {
/**
* User defined literals to represent constant values
* @example 42_i, 0_uc, 1.23_d
*/
constexpr auto operator""_i; /// int
constexpr auto operator""_s; /// short
constexpr auto operator""_c; /// char
constexpr auto operator""_l; /// long
constexpr auto operator""_ll; /// long long
constexpr auto operator""_u; /// unsigned
constexpr auto operator""_uc; /// unsigned char
constexpr auto operator""_us; /// unsigned short
constexpr auto operator""_ul; /// unsigned long
constexpr auto operator""_f; /// float
constexpr auto operator""_d; /// double
constexpr auto operator""_ld; /// long double
/**
* Represents dynamic values
* @example _i(42), _f(42.)
*/
constexpr auto _b(bool);
constexpr auto _c(char);
constexpr auto _s(short);
constexpr auto _i(int);
constexpr auto _l(long);
constexpr auto _ll(long long);
constexpr auto _u(unsigned);
constexpr auto _uc(unsigned char);
constexpr auto _us(unsigned short);
constexpr auto _ul(unsigned long);
constexpr auto _f(float);
constexpr auto _d(double);
constexpr auto _ld(long double);
/**
* Logical representation of constant boolean (true) value
* @example "is set"_b : true
* not "is set"_b : false
*/
constexpr auto operator ""_b;
} // namespace literals
inline namespace operators {
/**
* Comparison functions to be used in expressions
* @example eq(42, 42), neq(1, 2)
*/
constexpr auto eq(Operator lhs, Operator rhs); /// ==
constexpr auto neq(Operator lhs, Operator rhs); /// !=
constexpr auto gt(Operator lhs, Operator rhs); /// >
constexpr auto ge(Operator lhs, Operator rhs); /// >=
constexpr auto lt(Operator lhs, Operator rhs); /// <
constexpr auto le(Operator lhs, Operator rhs); /// <=
/**
* Overloaded comparison operators to be used in expressions
* @example (42_i != 0)
*/
constexpr auto operator==;
constexpr auto operator!=;
constexpr auto operator>;
constexpr auto operator>=;
constexpr auto operator<;
constexpr auto operator<=;
/**
* Overloaded logic operators to be used in expressions
* @example (42_i != 0 and 1 == 2_i)
*/
constexpr auto operator and;
constexpr auto operator or;
constexpr auto operator not;
/**
* Executes parameterized tests
* @example "parameterized"_test = [](auto arg) {} | std::tuple{1, 2, 3};
*/
constexpr auto operator|;
/**
* Creates tags
* @example tag("slow") / tag("nightly") / "perf"_test = []{};
*/
constexpr auto operator/;
/**
* Creates a `fatal_assertion` from an expression
* @example (42_i == 0) >> fatal
*/
constexpr auto operator>>;
} // namespace operators
/**
* Creates skippable test object
* @example skip / "don't run"_test = [] { };
*/
constexpr auto skip = tag("skip");
struct {
/**
* @example log << "message!";
* @param msg stringable message
*/
auto& operator<<(Msg msg);
} log{};
/**
* Makes object mutable
* @example mut(object)
* @param t object to be mutated
*/
template
| Option | Description | Example |
|-|-|-|
| `BOOST_UT_VERSION` | Current version | `1'1'9` |
API
Configuration
> `suite`
```cpp
/**
* Reperesents suite object
* @example suite _ = []{};
*/
struct suite final {
/**
* Assigns and executes test suite
*/
[[nodiscard]] constexpr explicit(false) suite(Suite suite) {
suite();
}
};
```
> `test`
```cpp
/**
* Creates named test object
* @example "hello world"_test
* @return test object
*/
[[nodiscard]] constexpr Test operator ""_test(const char* name, std::size_t size) {
return test{{name, size}};
}
```
```cpp
/**
* Represents test object
*/
struct test final {
std::string_view name{}; /// test case name
/**
* Assigns and executes test function
* @param test function
*/
constexpr auto operator=(const Test& test) {
std::cout << "Running... " << name << '\n';
test();
}
};
```
> `expect`
```cpp
/**
* Evaluates an expression
* @example expect(42_i == 42);
* @param expr expression to be evaluated
* @param source location https://en.cppreference.com/w/cpp/utility/source_location
* @return stream
*/
constexpr OStream& expect(
Expression expr,
const std::source_location& location = std::source_location::current()
) {
if (not static_cast
> Implementation
* Leveraging [C++20](#cpp-20) features
* Avoiding unique types for lambda expressions
```cpp
template
* API
* [Source Location](https://eel.is/c++draft/support.srcloc#source.location.syn)
* Assertions - `expect(false)` - ` __FILE__:__LINE__:FAILED [false]`
* [Designated initializers](https://eel.is/c++draft/dcl.init#nt:designated-initializer-list)
* Configuration - `cfg
> Parameterized tests with Expansion statements (https://wg21.link/P1306r1)
```cpp
template for (auto arg : std::tuple
> Personally, I believe that C++ standard could benefit from common testing primitives (`expect`, `""_test`) because
* It lowers the entry-level to the language (no need for third-party libraries)
* It improves the education aspect (one standard way of doing it)
* It makes the language more coherent/stable (consistent design with other features, stable API)
* It makes the testing a first class citizen (shows that the community cares about this aspect of the language)
* It allows to publish tests for the Standard Library (STL) in the standard way (coherency, easier to extend)
* It allows to act as additional documentation as a way to verify whether a particular implementation is conforming (quality, self-verification)
* It helps with establishing standard vocabulary for testing (common across STL and other projects)
> Sure, although please notice that there are negatives of using macros such as
* Error messages might be not clear and/or point to the wrong line
* Global scope will be polluted
* Type safety will be ignored
```cpp
#define EXPECT(...) ::boost::ut::expect(::boost::ut::that % __VA_ARGS__)
#define SUITE ::boost::ut::suite _ = []
#define TEST(name) ::boost::ut::detail::test{"test", name} = [=]() mutable
SUITE {
TEST("suite") {
EXPECT(42 == 42);
};
};
int main() {
TEST("macro") {
EXPECT(1 != 2);
};
TEST("vector") {
std::vector
> Consider using one of the following frameworks
* https://github.com/cpp-testing/GUnit/blob/master/docs/GMock.md
* https://github.com/eranpeer/FakeIt
* https://github.com/dascandy/hippomocks
> [Example benchmark](example/benchmark.cpp)
> Consider using one of the following frameworks
* https://github.com/google/benchmark
* https://github.com/DigitalInBlue/Celero
* https://github.com/libnonius/nonius
* https://github.com/martinus/nanobench
* [[Boost].UT - Unit Testing Framework - Kris Jusiak](https://boost-ext.github.io/ut/denver-cpp-2019)
* [Future of Testing with C++20 - Kris Jusiak](https://boost-ext.github.io/ut/meeting-cpp-2020)
* [Macro-Free Testing with C++20 - Kris Jusiak](https://www.youtube.com/watch?v=irdgFyxOs_Y)
* ["If you liked it then you `"should have put a"_test` on it", Beyonce rule - Kris Jusiak](https://www.youtube.com/watch?v=yCI8MjvOMeE)
* [Principles of Unit Testing With C++ - Dave Steffen and Kris Jusiak](https://www.youtube.com/watch?v=oOcuJdJJ33g)
* [Empirical Unit Testing - Dave Steffen](https://www.twitch.tv/videos/686512433)
> [CONTRIBUTING](.github/CONTRIBUTING.md)
How does it work?
Fast compilation times (Benchmarks)?
C++20 features?
C++2X integration?
Is standardization an option?
Can I still use macros?
What about Mocks/Stubs/Fakes?
What about Microbenchmarking?
Related materials/talks?
How to contribute?
| Framework | Version | Standard | License | Linkage | Test configuration | |-|-|-|-|-|-| | [Boost.Test](https://github.com/boostorg/test) | [1.71.0](https://www.boost.org/users/history/version_1_71_0.html) | C++03 | Boost 1.0 | single header/library | `static library` | | [GoogleTest](https://github.com/google/googletest) | [1.10.0](https://github.com/google/googletest/releases/tag/release-1.10.0) | C++11 | BSD-3 | library | `static library` | | [Catch](https://github.com/catchorg/Catch2) | [2.10.2](https://github.com/catchorg/Catch2/releases/download/v2.10.2/catch.hpp) | C++11 | Boost 1.0 | single header | `CATCH_CONFIG_FAST_COMPILE` | | [Doctest](https://github.com/onqtam/doctest) | [2.3.5](https://github.com/onqtam/doctest/blob/master/doctest/doctest.h) | C++11 | MIT | single header | `DOCTEST_CONFIG_SUPER_FAST_ASSERTS` | | [UT](https://github.com/boost-ext/ut) | [1.1.0](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp) | C++20 | Boost 1.0 | single header/module | |
Include / 0 tests, 0 asserts, 1 cpp file | |||
Assert / 1 test, 1'000'000 asserts, 1 cpp file | |||
Test / 1'000 tests, 0 asserts, 1 cpp file | |||
Suite / 10'000 tests, 0 asserts, 100 cpp files | |||
Suite+Assert / 10'000 tests, 40'000 asserts, 100 cpp files | |||
Suite+Assert+STL / 10'000 tests, 20'000 asserts, 100 cpp files | |||
Incremental Build - Suite+Assert+STL / 1 cpp file change (1'000 tests, 20'000 asserts, 100 cpp files) | |||
Suite+Assert+STL / 10'000 tests, 20'000 asserts, 100 cpp files (Headers vs Precompiled headers vs C++20 Modules) |
|||