{"id":13521325,"url":"https://github.com/boost-ext/ut","last_synced_at":"2025-10-07T22:47:57.173Z","repository":{"id":37044947,"uuid":"213094069","full_name":"boost-ext/ut","owner":"boost-ext","description":"C++20 μ(micro)/Unit Testing framework","archived":false,"fork":false,"pushed_at":"2025-07-07T23:22:35.000Z","size":5563,"stargazers_count":1346,"open_issues_count":93,"forks_count":134,"subscribers_count":25,"default_branch":"master","last_synced_at":"2025-07-08T01:33:33.696Z","etag":null,"topics":["bdd","cpp20","tdd","testing"],"latest_commit_sha":null,"homepage":"https://boost-ext.github.io/ut/","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/boost-ext.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-10-06T01:33:26.000Z","updated_at":"2025-07-07T23:22:39.000Z","dependencies_parsed_at":"2023-07-12T15:45:46.189Z","dependency_job_id":"37248f9f-4136-45f9-a65f-951e9329fe39","html_url":"https://github.com/boost-ext/ut","commit_stats":{"total_commits":481,"total_committers":49,"mean_commits":9.816326530612244,"dds":0.2224532224532224,"last_synced_commit":"20f2c3f811e25b8c398c6e98545dbf07d72eb4e8"},"previous_names":["boost-experimental/ut"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/boost-ext/ut","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boost-ext%2Fut","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boost-ext%2Fut/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boost-ext%2Fut/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boost-ext%2Fut/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/boost-ext","download_url":"https://codeload.github.com/boost-ext/ut/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boost-ext%2Fut/sbom","scorecard":{"id":247950,"data":{"date":"2025-08-11","repo":{"name":"github.com/boost-ext/ut","commit":"41a17aa3306767189a16c23759dad19ea5629b6d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5,"checks":[{"name":"Code-Review","score":8,"reason":"Found 16/19 approved changesets -- score normalized to 8","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":4,"reason":"5 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 4","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/emscripten.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/emscripten.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/install.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/install.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/install.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/install.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/install_download_all.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/install_download_all.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/install_download_all.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/install_download_all.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linux.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/linux.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linux.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/linux.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/macos.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/macos.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/macos.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/macos.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/windows.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/windows.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/windows.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/windows.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/windows_shared.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/windows_shared.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/windows_shared.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/boost-ext/ut/windows_shared.yml/master?enable=pin","Info:   0 out of  13 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/emscripten.yml:1","Warn: no topLevel permission defined: .github/workflows/install.yml:1","Warn: no topLevel permission defined: .github/workflows/install_download_all.yml:1","Warn: no topLevel permission defined: .github/workflows/linux.yml:1","Warn: no topLevel permission defined: .github/workflows/macos.yml:1","Warn: no topLevel permission defined: .github/workflows/windows.yml:1","Warn: no topLevel permission defined: .github/workflows/windows_shared.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: Boost Software License 1.0: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 29 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T07:56:44.426Z","repository_id":37044947,"created_at":"2025-08-17T07:56:44.426Z","updated_at":"2025-08-17T07:56:44.426Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278861030,"owners_count":26058632,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bdd","cpp20","tdd","testing"],"created_at":"2024-08-01T06:00:32.769Z","updated_at":"2025-10-07T22:47:57.144Z","avatar_url":"https://github.com/boost-ext.png","language":"C++","readme":"\u003ca href=\"https://conan.io/center/boost-ext-ut\" target=\"_blank\"\u003e![Version](https://img.shields.io/github/v/release/boost-ext/ut)\u003c/a\u003e\n\u003ca href=\"https://github.com/boost-ext/ut/actions/workflows/linux.yml\" target=\"_blank\"\u003e![Linux](https://github.com/boost-ext/ut/actions/workflows/linux.yml/badge.svg)\u003c/a\u003e\n\u003ca href=\"https://github.com/boost-ext/ut/actions/workflows/macos.yml\" target=\"_blank\"\u003e![MacOs](https://github.com/boost-ext/ut/actions/workflows/macos.yml/badge.svg)\u003c/a\u003e\n\u003ca href=\"https://github.com/boost-ext/ut/actions/workflows/windows.yml\" target=\"_blank\"\u003e![Windows](https://github.com/boost-ext/ut/actions/workflows/windows.yml/badge.svg)\u003c/a\u003e\n\u003ca href=\"https://codecov.io/gh/boost-ext/ut\" target=\"_blank\"\u003e![Coveralls](https://codecov.io/gh/boost-ext/ut/branch/master/graph/badge.svg)\u003c/a\u003e\n\u003ca href=\"https://godbolt.org/z/f4jEcv9vo\"\u003e![Try it online](https://img.shields.io/badge/try%20it-online-blue.svg)\u003c/a\u003e\n\u003ca href=\"https://aur.archlinux.org/packages/ut/\"\u003e![AUR Badge](https://img.shields.io/aur/version/ut)\u003c/a\u003e\n\n\u003e \"If you liked it then you `\"should have put a\"_test` on it\", Beyonce rule\n\n# UT / μt\n\n| [Motivation](#motivation) | [Quick Start](#quick-start) | [Overview](#overview) | [Tutorial](#tutorial) | [Examples](#examples) | [User Guide](#user-guide) | [FAQ](#faq) | [Benchmarks](#benchmarks) |\n\n\u003cdetails open\u003e\u003csummary\u003eC++ \u003cb\u003esingle header/single module, macro-free\u003c/b\u003e μ(micro)/Unit Testing Framework\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\n#include \u003cboost/ut.hpp\u003e // import boost.ut;\n\nconstexpr auto sum(auto... values) { return (values + ...); }\n\nint main() {\n  using namespace boost::ut;\n\n  \"sum\"_test = [] {\n    expect(sum(0) == 0_i);\n    expect(sum(1, 2) == 3_i);\n    expect(sum(1, 2) \u003e 0_i and 41_i == sum(40, 2));\n  };\n}\n```\n\n```sh\nRunning \"sum\"...\n  sum.cpp:11:FAILED [(3 \u003e 0 and 41 == 42)]\nFAILED\n\n===============================================================================\ntests:   1 | 1 failed\nasserts: 3 | 2 passed | 1 failed\n```\n\n\u003e https://godbolt.org/z/f4jEcv9vo\n\n\u003ca name=\"motivation\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003eMotivation\u003c/summary\u003e\n\u003cp\u003e\n\nTesting is a very important part of the Software Development, however, C++ doesn't provide any good testing facilities out of the box,\nwhich often leads into a poor testing experience for develops and/or lack of tests/coverage in general.\n\n\u003e One should treat testing code as production code!\n\nAdditionally, 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.\n\nThe following snippet is a common example of testing with projects in C++.\n\n```cpp\nint main() {\n  // should sum numbers\n  {\n    assert(3 == sum(1, 2));\n  }\n}\n```\n\nThere are quite a few problems with the approach above\n\n* No names for tests (Hard to follow intentions by further readers)\n* No automatic registration of tests (No way to run specific tests)\n* Hard to debug (Assertions don't provide any information why it failed)\n* Hard to scale (No easy path forward for parameterized tests, multiple suites, parallel execution, etc...)\n* Hard to integrate (No easy way to have a custom output such as XML for CI integration)\n* Easy to make mistakes (With implicit casting, floating point comparison, pointer comparison for strings, etc...)\n* Hard to follow good practises such as `TDD/BDD` (Lack of support for sections and declarative expressions)\n* ...\n\n`UT` is trying to address these issues by simplifying testing experience with a few simple steps:\n\n* Just get a single [header](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp) or [module+header](https://github.com/boost-ext/ut/blob/master/include/boost/ut.cppm)\n* Integrate it into your project\n* Learn a few simple concepts ([expect, test, suite](#api))\n\nAnd you good to go!\n\nOkay, great, but why I would use `UT` over other/similar testing frameworks already available in C++?\n\n* [Boost.Test](https://github.com/boostorg/test)\n* [GoogleTest](https://github.com/google/googletest)\n* [Catch](https://github.com/catchorg/Catch2)\n* [...](https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C++)\n\nGreat question! There are a few unique features which makes `UT` worth trying\n\n* Firstly, it supports all the basic Unit Testing Framework features (automatic registration of tests, assertions, suites, etc...)\n* It's easy to integrate (it's just one [header/module](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp))\n* 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))\n* 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)\n* It has smaller learning curve (just a few simple concepts ([expect, test, suite](#api)))\n* It leverages C++ features to support more complex testing ([parameterized](#examples))\n* It's faster to compile and execute than similar frameworks which makes it suitable for bigger projects without additional hassle ([Benchmarks](#benchmarks))\n* It supports [TDD/BDD](#examples) workflows\n* It supports [Gherkin](#examples) specification\n* It supports [Spec](#examples)\n* ...\n\nSounds intriguing/interesting? Learn more at\n\n* [Tutorial](#tutorial)\n* [Examples](#examples)\n* [User-Guide](#user-guide)\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"quick-start\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003eQuick Start\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e https://bit.ly/ut-quick-start (slides)\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"overview\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003eOverview\u003c/summary\u003e\n\u003cp\u003e\n\n* No dependencies ([C++20](#cpp-20), Tested Compilers: GCC-9+, Clang-9.0+, Apple Clang-11.0.0+, MSVC-2019+*, Clang-cl-9.0+\n* Single header/module ([boost/ut.hpp](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp))\n* Macro-free ([How does it work?](#how-it-works))\n* Easy to use ([Minimal API](#api) - `test, suite, operators, literals, [expect]`)\n* Fast to compile/execute ([Benchmarks](#benchmarks))\n* 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))\n* Integrations ([ApprovalTests.cpp](https://github.com/approvals/ApprovalTests.cpp/releases/tag/v.7.0.0))\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"tutorial\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003eTutorial\u003c/summary\u003e\n\u003cp\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Step 0: Get it...\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e Get the latest latest header/module from [here!](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp)\n\n\u003e Include/Import\n\n```cpp\n// #include \u003cboost/ut.hpp\u003e // single header\n// import boost.ut;        // single module (C++20)\n\nint main() { }\n```\n\n\u003e Compile \u0026 Run\n\n```\n$CXX main.cpp \u0026\u0026 ./a.out\n```\n\n```\nAll tests passed (0 assert in 0 test)\n```\n\n\u003e [Optional] Install it\n\n```\ncmake -Bbuild -H.\ncd build \u0026\u0026 make         # run tests\ncd build \u0026\u0026 make install # install\n```\n\n\u003e [Optional] CMake integration\n\nThis project provides a CMake config and target.\nJust load `ut` with `find_package` to import the `Boost::ut` target.\nLinking against this target will add the necessary include directory for the single header file.\nThis is demonstrated in the following example.\n\n```cmake\nfind_package(ut REQUIRED)\nadd_library(my_test my_test.cpp)\ntarget_link_libraries(my_test PRIVATE Boost::ut)\n```\n\n\u003e [Optional] [Conan](https://conan.io) integration\n\nThe [boost-ext-ut](https://conan.io/center/boost-ext-ut) package is available from [Conan Center](https://conan.io/center/).\nJust include it in your project's Conanfile with `boost-ext-ut/2.3.1`.\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Step 1: Expect it...\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e Let's write our first assertion, shall we?\n\n```cpp\nint main() {\n  boost::ut::expect(true);\n}\n```\n\n```\nAll tests passed (1 asserts in 0 test)\n```\n\n\u003e https://godbolt.org/z/vfx-eB\n\n\n\u003e Okay, let's make it fail now?\n\n```cpp\nint main() {\n  boost::ut::expect(1 == 2);\n}\n```\n\n```\nmain.cpp:4:FAILED [false]\n===============================================================================\ntests:   0 | 0 failed\nasserts: 1 | 0 passed | 1 failed\n```\n\n\u003e https://godbolt.org/z/7qTePx\n\n\u003e Notice that expression `1 == 2` hasn't been printed. Instead we got `false`?\n\n\u003e Let's print it then?\n\n```cpp\nint main() {\n  using namespace boost::ut;\n  expect(1_i == 2);\n}\n```\n\n```\nmain.cpp:4:FAILED [1 == 2]\n===============================================================================\ntests:   0 | 0 failed\nasserts: 1 | 0 passed | 1 failed\n```\n\n\u003e https://godbolt.org/z/7MXVzu\n\n\u003e Okay, now we have it! `1 == 2` has been printed as expected.\n\u003e Notice the User Defined Literal (UDL) `1_i` was used.\n\u003e `_i` is a compile-time constant integer value\n\n* It allows to override comparison operators 👍\n* It disallow comparison of different types 👍\n\nSee the [User-guide](#user-guide) for more details.\n\n\u003e Alternatively, a `terse` notation (no expect required) can be used.\n\n```cpp\nint main() {\n  using namespace boost::ut::literals;\n  using namespace boost::ut::operators::terse;\n\n  1_i == 2; // terse notation\n}\n```\n\n```\nmain.cpp:7:FAILED [1 == 2]\n===============================================================================\ntests:   0 | 0 failed\nasserts: 1 | 0 passed | 1 failed\n```\n\n\u003e https://godbolt.org/z/s77GSm\n\n\u003e Other expression syntaxes are also available.\n\n```cpp\nexpect(1_i == 2);       // UDL syntax\nexpect(1 == 2_i);       // UDL syntax\nexpect(that % 1 == 2);  // Matcher syntax\nexpect(eq(1, 2));       // eq/neq/gt/ge/lt/le\n```\n\n```\nmain.cpp:6:FAILED [1 == 2]\nmain.cpp:7:FAILED [1 == 2]\nmain.cpp:8:FAILED [1 == 2]\nmain.cpp:9:FAILED [1 == 2]\n===============================================================================\ntests:   0 | 0 failed\nasserts: 4 | 0 passed | 4 failed\n```\n\n\u003e https://godbolt.org/z/QbgGtc\n\n\u003e Okay, but what about the case if my assertion is fatal.\n\u003e Meaning that the program will crash unless the processing will be terminated.\n\u003e Nothing easier, let's just add `fatal` call to make the test fail immediately.\n\n```cpp\nexpect(fatal(1 == 2_i)); // fatal assertion\nexpect(1_i == 2);        // not executed\n```\n\n```\nmain.cpp:6:FAILED [1 == 2]\n===============================================================================\ntests:   1 | 1 failed\nasserts: 2 | 0 passed | 2 failed\n```\n\n\u003e https://godbolt.org/z/WMe8Y1\n\n\u003e But my expression is more complex than just simple comparisons.\n\u003e Not a problem, logic operators are also supported in the `expect` 👍.\n\n```cpp\nexpect(42l == 42_l and 1 == 2_i); // compound expression\n```\n\n```\nmain.cpp:5:FAILED [(42 == 42 and 1 == 2)]\n===============================================================================\ntests:   0 | 0 failed\nasserts: 1 | 0 passed | 1 failed\n```\n\n\u003e https://godbolt.org/z/aEhX4t\n\n\u003e Can I add a custom message though?\n\u003e Sure, `expect` calls are streamable!\n\n```cpp\nint main() {\n  expect(42l == 42_l and 1 == 2_i) \u003c\u003c \"additional info\";\n}\n```\n\n```\nmain.cpp:5:FAILED [(42 == 42 and 1 == 2)] additional info\n===============================================================================\ntests:   0 | 0 failed\nasserts: 1 | 0 passed | 1 failed\n```\n\n\u003e That's nice, can I use custom messages and fatal assertions?\n\u003e Yes, stream the `fatal`!\n\n```cpp\nexpect(fatal(1 == 2_i)) \u003c\u003c \"fatal assertion\";\nexpect(1_i == 2);\n```\n\n```\nFAILED\nin: main.cpp:6 - test condition:  [1 == 2]\n\n fatal assertion\n===============================================================================\ntests:   0 | 2 failed\nasserts: 0 | 0 passed | 2 failed\n```\n\n\u003e I use `std::expected`, can I stream its `error()` upon failure?\n\u003e Yes, since `std::expected`'s `error()` can only be called when there is no\n\u003e value it requires lazy evaluation.\n\n```cpp\n\"lazy log\"_test = [] {\n  std::expected\u003cbool, std::string\u003e e = std::unexpected(\"lazy evaluated\");\n  expect(e.has_value()) \u003c\u003c [\u0026] { return e.error(); } \u003c\u003c fatal;\n  expect(e.value() == true);\n};\n\n```\n\n```\nRunning test \"lazy log\"... FAILED\nin: main.cpp:12 - test condition:  [false]\n\n lazy evaluated\n===============================================================================\ntests:   1 | 2 failed\nasserts: 0 | 0 passed | 2 failed\n```\n\n\u003e https://godbolt.org/z/v2PDuU\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Step 2: Group it...\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e Assertions are great, but how to combine them into more cohesive units?\n\u003e `Test cases` are the way to go! They allow to group expectations for the same functionality into coherent units.\n\n```cpp\n\"hello world\"_test = [] { };\n```\n\n\u003e Alternatively `test(\"hello world\") = [] {}` can be used.\n\n```\nAll tests passed (0 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/Bh-EmY\n\n\n\u003e Notice `1 tests` but `0 asserts`.\n\n\u003e Let's make our first end-2-end test case, shall we?\n\n```cpp\nint main() {\n  \"hello world\"_test = [] {\n    int i = 43;\n    expect(42_i == i);\n  };\n}\n```\n\n```\nRunning \"hello world\"...\n  main.cpp:8:FAILED [42 == 43]\nFAILED\n===============================================================================\ntests:   1 | 1 failed\nasserts: 1 | 0 passed | 1 failed\n```\n\n\u003e https://godbolt.org/z/Y43mXz\n\n\u003e 👍 We are done here!\n\n\u003e I'd like to nest my tests, though and share setup/tear-down.\n\u003e With lambdas used to represents `tests/sections` we can easily achieve that.\n\u003e Let's just take a look at the following example.\n\n```cpp\nint main() {\n  \"[vector]\"_test = [] {\n    std::vector\u003cint\u003e v(5);\n\n    expect(fatal(5_ul == std::size(v)));\n\n    should(\"resize bigger\") = [v] { // or \"resize bigger\"_test\n      mut(v).resize(10);\n      expect(10_ul == std::size(v));\n    };\n\n    expect(fatal(5_ul == std::size(v)));\n\n    should(\"resize smaller\") = [=]() mutable { // or \"resize smaller\"_test\n      v.resize(0);\n      expect(0_ul == std::size(v));\n    };\n  }\n}\n```\n\n```\nAll tests passed (4 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/XWAdYt\n\n\u003e Nice! That was easy, but I'm a believer into Behaviour Driven Development (`BDD`).\n\u003e Is there a support for that?\n\u003e Yes! Same example as above just with the `BDD` syntax.\n\n```cpp\nint main() {\n  \"vector\"_test = [] {\n    given(\"I have a vector\") = [] {\n      std::vector\u003cint\u003e v(5);\n      expect(fatal(5_ul == std::size(v)));\n\n      when(\"I resize bigger\") = [=] {\n        mut(v).resize(10);\n\n        then(\"The size should increase\") = [=] {\n          expect(10_ul == std::size(v));\n        };\n      };\n    };\n  };\n}\n```\n\n```\nAll tests passed (2 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/dnvxsE\n\n\u003e On top of that, `feature/scenario` aliases can be leveraged.\n\n```cpp\nint main() {\n  feature(\"vector\") = [] {\n    scenario(\"size\") = [] {\n      given(\"I have a vector\") = [] {\n        std::vector\u003cint\u003e v(5);\n        expect(fatal(5_ul == std::size(v)));\n\n        when(\"I resize bigger\") = [=] {\n          mut(v).resize(10);\n\n          then(\"The size should increase\") = [=] {\n            expect(10_ul == std::size(v));\n          };\n        };\n      };\n    };\n  };\n}\n```\n\n```\nAll tests passed (2 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/T4cWss\n\n\u003e Can I use `Gherkin`?\n\u003e Yeah, let's rewrite the example using `Gherkin` specification\n\n```cpp\nint main() {\n  bdd::gherkin::steps steps = [](auto\u0026 steps) {\n    steps.feature(\"Vector\") = [\u0026] {\n      steps.scenario(\"*\") = [\u0026] {\n        steps.given(\"I have a vector\") = [\u0026] {\n          std::vector\u003cint\u003e v(5);\n          expect(fatal(5_ul == std::size(v)));\n\n          steps.when(\"I resize bigger\") = [\u0026] {\n            v.resize(10);\n          };\n\n          steps.then(\"The size should increase\") = [\u0026] {\n            expect(10_ul == std::size(v));\n          };\n        };\n      };\n    };\n  };\n\n  \"Vector\"_test = steps |\n    R\"(\n      Feature: Vector\n        Scenario: Resize\n          Given I have a vector\n           When I resize bigger\n           Then The size should increase\n    )\";\n}\n```\n\n```\nAll tests passed (2 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/jb1d8P\n\n\u003e Nice, is `Spec` notation supported as well?\n\n```cpp\nint main() {\n  describe(\"vector\") = [] {\n    std::vector\u003cint\u003e v(5);\n    expect(fatal(5_ul == std::size(v)));\n\n    it(\"should resize bigger\") = [v] {\n      mut(v).resize(10);\n      expect(10_ul == std::size(v));\n    };\n  };\n}\n```\n\n```\nAll tests passed (2 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/6jKKzT\n\n\u003e That's great, but how can call the same tests with different arguments/types to be DRY (Don't Repeat Yourself)?\n\u003e Parameterized tests to the rescue!\n\n```cpp\nint main() {\n  for (auto i : std::vector{1, 2, 3}) {\n    test(\"parameterized \" + std::to_string(i)) = [i] { // 3 tests\n      expect(that % i \u003e 0); // 3 asserts\n    };\n  }\n}\n```\n\n```\nAll tests passed (3 asserts in 3 tests)\n```\n\n\u003e https://godbolt.org/z/Utnd6X\n\n\u003e That's it 😮!\n\u003e Alternatively, a convenient test syntax is also provided 👍\n\n```cpp\nint main() {\n  \"args\"_test = [](const auto\u0026 arg) {\n    expect(arg \u003e 0_i) \u003c\u003c \"all values greater than 0\";\n  } | std::vector{1, 2, 3};\n}\n```\n\n```\nAll tests passed (3 asserts in 3 tests)\n```\n\n\u003e https://godbolt.org/z/6FHtpq\n\n\u003e Check [Examples](#examples) for further reading.\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Step 3: Scale it...\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e Okay, but my project is more complex than that. How can I scale?\n\u003e `Test suites` will make that possible. By using `suite` in translation units\n\u003e `tests` defined inside will be automatically registered 👍\n\n```cpp\nsuite errors = [] { // or suite\u003c\"nameofsuite\"\u003e\n  \"exception\"_test = [] {\n    expect(throws([] { throw 0; })) \u003c\u003c \"throws any exception\";\n  };\n\n  \"failure\"_test = [] {\n    expect(aborts([] { assert(false); }));\n  };\n};\n\nint main() { }\n```\n\n```\nAll tests passed (2 asserts in 2 tests)\n```\n\n\u003e https://godbolt.org/z/_ccGwZ\n\n---\n\n\u003e What's next?\n\u003e * [Examples](#examples)\n\u003e * [User-Guide](#user-guide)\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"examples\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003eExamples\u003c/summary\u003e\n\u003cp\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Assertions\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\n// operators\nexpect(0_i == sum());\nexpect(2_i != sum(1, 2));\nexpect(sum(1) \u003e= 0_i);\nexpect(sum(1) \u003c= 1_i);\n```\n\n```cpp\n// message\nexpect(3_i == sum(1, 2)) \u003c\u003c \"wrong sum\";\n```\n\n```cpp\n// expressions\nexpect(0_i == sum() and 42_i == sum(40, 2));\nexpect(0_i == sum() or 1_i == sum()) \u003c\u003c \"compound\";\n```\n\n```cpp\n// matchers\nexpect(that % 0 == sum());\nexpect(that % 42 == sum(40, 2) and that % (1 + 2) == sum(1, 2));\nexpect(that % 1 != 2 or 2_i \u003e 3);\n```\n\n```cpp\n// eq/neq/gt/ge/lt/le\nexpect(eq(42, sum(40, 2)));\nexpect(neq(1, 2));\nexpect(eq(sum(1), 1) and neq(sum(1, 2), 2));\nexpect(eq(1, 1) and that % 1 == 1 and 1_i == 1);\n```\n\n```cpp\n// floating points\nexpect(42.1_d == 42.101) \u003c\u003c \"epsilon=0.1\";\nexpect(42.10_d == 42.101) \u003c\u003c \"epsilon=0.01\";\nexpect(42.10000001 == 42.1_d) \u003c\u003c \"epsilon=0.1\";\n```\n\n```cpp\n// constant\nconstexpr auto compile_time_v = 42;\nauto run_time_v = 99;\nexpect(constant\u003c42_i == compile_time_v\u003e and run_time_v == 99_i);\n```\n\n```cpp\n// failure\nexpect(1_i == 2) \u003c\u003c \"should fail\";\nexpect(sum() == 1_i or 2_i == sum()) \u003c\u003c \"sum?\";\n```\n\n```\nassertions.cpp:53:FAILED [1 == 2] should fail\nassertions.cpp:54:FAILED [(0 == 1 or 2 == 0)] sum?\n===============================================================================\ntests:   0  | 0 failed\nasserts: 20 | 18 passed | 2 failed\n```\n\n\u003e https://godbolt.org/z/E1c7G5\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Tests\u003c/summary\u003e\n\u003cp\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Run/Skip/Tag\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\n\"run UDL\"_test = [] {\n  expect(42_i == 42);\n};\n\nskip / \"don't run UDL\"_test = [] {\n  expect(42_i == 43) \u003c\u003c \"should not fire!\";\n};\n```\n\n```\nAll tests passed (1 asserts in 1 tests)\n1 tests skipped\n```\n\n```cpp\ntest(\"run function\") = [] {\n  expect(42_i == 42);\n};\n\nskip / test(\"don't run function\") = [] {\n  expect(42_i == 43) \u003c\u003c \"should not fire!\";\n};\n```\n\n```\nAll tests passed (1 asserts in 1 tests)\n1 tests skipped\n```\n\n```cpp\ntag(\"nightly\") / tag(\"slow\") /\n\"performance\"_test= [] {\n  expect(42_i == 42);\n};\n\ntag(\"slow\") /\n\"run slowly\"_test= [] {\n  expect(42_i == 43) \u003c\u003c \"should not fire!\";\n};\n```\n\n```\ncfg\u003coverride\u003e = {.tag = {\"nightly\"}};\n```\n\n```\nAll tests passed (1 asserts in 1 tests)\n1 tests skipped\n```\n\n\u003e https://godbolt.org/z/X3_kG4\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Sections\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\n\"[vector]\"_test = [] {\n  std::vector\u003cint\u003e v(5);\n\n  expect(fatal(5_ul == std::size(v)));\n\n  should(\"resize bigger\") = [=] { // or \"resize bigger\"_test\n    mut(v).resize(10);\n    expect(10_ul == std::size(v));\n  };\n\n  expect(fatal(5_ul == std::size(v)));\n\n  should(\"resize smaller\") = [=]() mutable { // or \"resize smaller\"_test\n    v.resize(0);\n    expect(0_ul == std::size(v));\n  };\n};\n```\n\n```\nAll tests passed (4 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/cE91bj\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Behavior Driven Development (BDD)\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\n\"Scenario\"_test = [] {\n  given(\"I have...\") = [] {\n    when(\"I run...\") = [] {\n      then(\"I expect...\") = [] { expect(1_i == 1); };\n      then(\"I expect...\") = [] { expect(1 == 1_i); };\n    };\n  };\n};\n```\n\n```\nAll tests passed (2 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/mNBySr\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Gherkin\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\nint main() {\n  bdd::gherkin::steps steps = [](auto\u0026 steps) {\n    steps.feature(\"*\") = [\u0026] {\n      steps.scenario(\"*\") = [\u0026] {\n        steps.given(\"I have a number {value}\") = [\u0026](int value) {\n          auto number = value;\n          steps.when(\"I add {value} to it\") = [\u0026](int value) {\n            number += value;\n          };\n          steps.then(\"I expect number to be {value}\") = [\u0026](int value) {\n            expect(that % number == value);\n          };\n        };\n      };\n    };\n  };\n\n  \"Gherkin\"_test = steps |\n    R\"(\n      Feature: Number\n        Scenario: Addition\n          Given I have a number 40\n           When I add 2 to it\n           Then I expect number to be 42\n    )\";\n}\n```\n\n```\nAll tests passed (1 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/BP3hyt\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Spec\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\nint main() {\n  describe(\"equality\") = [] {\n    it(\"should be equal\")     = [] { expect(0_i == 0); };\n    it(\"should not be equal\") = [] { expect(1_i != 0); };\n  };\n}\n```\n\n```\nAll tests passed (2 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/BXYJ3a\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Parameterized\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\nfor (auto i : std::vector{1, 2, 3}) {\n  test(\"parameterized \" + std::to_string(i)) = [i] {\n    expect(that % i \u003e 0);\n  };\n}\n\n\"args\"_test =\n   [](auto arg) {\n      expect(arg \u003e= 1_i);\n    }\n  | std::vector{1, 2, 3};\n\n\"types\"_test =\n    []\u003cclass T\u003e {\n      expect(std::is_integral_v\u003cT\u003e) \u003c\u003c \"all types are integrals\";\n    }\n  | std::tuple\u003cbool, int\u003e{};\n\n\"args and types\"_test =\n    []\u003cclass TArg\u003e(TArg arg) {\n      expect(fatal(std::is_integral_v\u003cTArg\u003e));\n      expect(42_i == arg or \"is true\"_b == arg);\n      expect(type\u003cTArg\u003e == type\u003cint\u003e or type\u003cTArg\u003e == type\u003cbool\u003e);\n    }\n  | std::tuple{true, 42};\n```\nWhen using the `operator|` syntax instead of a `for` loop, the test name will automatically\nbe extended to avoid duplicate names. For example, the test name for the `args and types` test\nwill be `args and types (true, bool)` for the first parameter and `args and types (42, int)`\nfor the second parameter. For simple built-in types (integral types and floating point numbers),\nthe test name will contain the parameter values. For other types, the parameters will simply be\nenumerated. For example, if we would extend the test above to use\n`std::tuple{true, 42, std::complex\u003cdouble\u003e{0.5, 1}}`, the test name in the third run would be\n`args and types (3rd parameter, std::complex\u003cdouble\u003e)`. If you want to have the actual value of\na non-integral type included in the test name, you can overload the `format_test_parameter` function.\nSee the [example on parameterized tests](https://github.com/boost-ext/ut/blob/master/example/parameterized.cpp)\nfor details.\n\n```\nAll tests passed (14 asserts in 10 tests)\n```\n\n\u003e https://godbolt.org/z/4xGGdo\n\n\n\u003e And whenever I need to know the specific type for which the test failed,\n\u003e I can use `reflection::type_name\u003cT\u003e()`, like this:\n\n```cpp\n\"types with type name\"_test =\n    []\u003cclass T\u003e() {\n      expect(std::is_unsigned_v\u003cT\u003e) \u003c\u003c reflection::type_name\u003cT\u003e() \u003c\u003c \"is unsigned\";\n    }\n  | std::tuple\u003cunsigned int, float\u003e{};\n```\n\n```\nRunning \"types with type name\"...PASSED\nRunning \"types with type name\"...\n  \u003csource\u003e:10:FAILED [false] float is unsigned\nFAILED\n```\n\n\u003e https://godbolt.org/z/MEnGnbTY4\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Suites\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\nnamespace ut = boost::ut;\n\nut::suite errors = [] {\n  using namespace ut;\n\n  \"throws\"_test = [] {\n    expect(throws([] { throw 0; }));\n  };\n\n  \"doesn't throw\"_test = [] {\n    expect(nothrow([]{}));\n  };\n};\n\nint main() { }\n```\n\n```\nAll tests passed (2 asserts in 2 tests)\n```\n\n\u003e https://godbolt.org/z/CFbTP9\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Misc\u003c/summary\u003e\n\u003cp\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Logging using streams\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\n\"logging\"_test = [] {\n  log \u003c\u003c \"pre\";\n  expect(42_i == 43) \u003c\u003c \"message on failure\";\n  log \u003c\u003c \"post\";\n};\n```\n\n```\nRunning \"logging\"...\npre\n  logging.cpp:8:FAILED [42 == 43] message on failure\npost\nFAILED\n\n===============================================================================\n\ntests:   1 | 1 failed\nasserts: 1 | 0 passed | 1 failed\n```\n\n\u003e https://godbolt.org/z/26fPSY\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Logging using formatting\u003c/summary\u003e\n\u003cp\u003e\nThis requires using C++20 with a standard library with std::format support.\n\n```cpp\n\"logging\"_test = [] {\n  log(\"\\npre  {} == {}\\n\", 42, 43);\n  expect(42_i == 43) \u003c\u003c \"message on failure\";\n  log(\"\\npost {} == {} -\u003e {}\\n\", 42, 43, 42 == 43);\n};\n```\n\n```\nRunning \"logging\"...\npre  42 == 43\n  logging.cpp:8:FAILED [42 == 43] message on failure\npost 42 == 43 -\u003e false\nFAILED\n\n===============================================================================\n\ntests:   1 | 1 failed\nasserts: 1 | 0 passed | 1 failed\n```\n\n\u003e https://godbolt.org/z/26fPSY\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Matchers\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\n\"matchers\"_test = [] {\n  constexpr auto is_between = [](auto lhs, auto rhs) {\n    return [=](auto value) {\n      return that % value \u003e= lhs and that % value \u003c= rhs;\n    };\n  };\n\n  expect(is_between(1, 100)(42));\n  expect(not is_between(1, 100)(0));\n};\n```\n\n```\nAll tests passed (2 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/4qwrCi\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Exceptions/Aborts\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\n\"exceptions/aborts\"_test = [] {\n  expect(throws\u003cstd::runtime_error\u003e([] { throw std::runtime_error{\"\"}; }))\n    \u003c\u003c \"throws runtime_error\";\n  expect(throws([] { throw 0; })) \u003c\u003c \"throws any exception\";\n  expect(nothrow([]{})) \u003c\u003c \"doesn't throw\";\n  expect(aborts([] { assert(false); }));\n};\n```\n\n```\nAll tests passed (4 asserts in 1 tests)\n```\n\n\u003e https://godbolt.org/z/A2EehK\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Config\u003c/summary\u003e\n\u003cp\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Runner\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\nnamespace ut = boost::ut;\n\nnamespace cfg {\n  class runner {\n   public:\n    template \u003cclass... Ts\u003e auto on(ut::events::test\u003cTs...\u003e test) { test(); }\n    template \u003cclass... Ts\u003e auto on(ut::events::skip\u003cTs...\u003e) {}\n    template \u003cclass TExpr\u003e\n    auto on(ut::events::assertion\u003cTExpr\u003e) -\u003e bool { return true; }\n    auto on(ut::events::fatal_assertion) {}\n    template \u003cclass TMsg\u003e auto on(ut::events::log\u003cTMsg\u003e) {}\n  };\n} // namespace cfg\n\ntemplate\u003c\u003e auto ut::cfg\u003cut::override\u003e = cfg::runner{};\n```\n\n\u003e https://godbolt.org/z/jdg687\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Reporter\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\nnamespace ut = boost::ut;\n\nnamespace cfg {\n  class reporter {\n   public:\n    auto on(ut::events::test_begin) -\u003e void {}\n    auto on(ut::events::test_run) -\u003e void {}\n    auto on(ut::events::test_skip) -\u003e void {}\n    auto on(ut::events::test_end) -\u003e void {}\n    template \u003cclass TMsg\u003e auto on(ut::events::log\u003cTMsg\u003e) -\u003e void {}\n    template \u003cclass TExpr\u003e\n    auto on(ut::events::assertion_pass\u003cTExpr\u003e) -\u003e void {}\n    template \u003cclass TExpr\u003e\n    auto on(ut::events::assertion_fail\u003cTExpr\u003e) -\u003e void {}\n    auto on(ut::events::fatal_assertion) -\u003e void {}\n    auto on(ut::events::exception) -\u003e void {}\n    auto on(ut::events::summary) -\u003e void {}\n  };\n}  // namespace cfg\n\ntemplate \u003c\u003e\nauto ut::cfg\u003cut::override\u003e = ut::runner\u003ccfg::reporter\u003e{};\n```\n\n\u003e https://godbolt.org/z/gsAPKg\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Printer\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\nnamespace ut = boost::ut;\n\nnamespace cfg {\nstruct printer : ut::printer {\n  template \u003cclass T\u003e\n  auto\u0026 operator\u003c\u003c(T\u0026\u0026 t) {\n    std::cerr \u003c\u003c std::forward\u003cT\u003e(t);\n    return *this;\n  }\n};\n}  // namespace cfg\n\ntemplate \u003c\u003e\nauto ut::cfg\u003cut::override\u003e = ut::runner\u003cut::reporter\u003ccfg::printer\u003e\u003e{};\n\nint main() {\n  using namespace ut;\n  \"printer\"_test = [] {};\n}\n```\n\n\u003e https://godbolt.org/z/XCscF9\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"api\"\u003e\u003c/a\u003e\n\u003ca name=\"configuration\"\u003e\u003c/a\u003e\n\u003ca name=\"user-guide\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003eUser Guide\u003c/summary\u003e\n\u003cp\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;API\u003c/summary\u003e\n\u003cp\u003e\n\n```cpp\nexport module boost.ut; /// __cpp_modules\n\nnamespace boost::inline ext::ut::inline v2_3_1 {\n  /**\n   * Represents test suite object\n   */\n  struct suite final {\n    /**\n     * Creates and executes test suite\n     * @example suite _ = [] {};\n     * @param suite test suite function\n     */\n    constexpr explicit(false) suite(auto suite);\n  };\n\n  /**\n   * Creates a test\n   * @example \"name\"_test = [] {};\n   * @return test object to be executed\n   */\n  constexpr auto operator\"\"_test;\n\n  /**\n   * Creates a test\n   * @example test(\"name\") = [] {};\n   * @return test object to be executed\n   */\n  constexpr auto test = [](const auto name);\n\n  /**\n   * Creates a test\n   * @example should(\"name\") = [] {};\n   * @return test object to be executed\n   */\n  constexpr auto should = [](const auto name);\n\n  /**\n   * Behaviour Driven Development (BDD) helper functions\n   * @param name step name\n   * @return test object to be executed\n   */\n  constexpr auto given = [](const auto name);\n  constexpr auto when  = [](const auto name);\n  constexpr auto then  = [](const auto name);\n\n  /**\n   * Evaluates an expression\n   * @example expect(42 == 42_i and 1 != 2_i);\n   * @param expr expression to be evaluated\n   * @param source location https://en.cppreference.com/w/cpp/utility/source_location\n   * @return stream\n   */\n  constexpr OStream\u0026 expect(\n    Expression expr,\n    const std::source_location\u0026 location = std::source_location::current()\n  );\n\n  struct {\n    /**\n     * @example (that % 42 == 42);\n     * @param expr expression to be evaluated\n     */\n    [[nodiscard]] constexpr auto operator%(Expression expr) const;\n  } that{};\n\n  inline namespace literals {\n    /**\n     * User defined literals to represent constant values\n     * @example 42_i, 0_uc, 1.23_d\n     */\n    constexpr auto operator\"\"_i;  /// int\n    constexpr auto operator\"\"_s;  /// short\n    constexpr auto operator\"\"_c;  /// char\n    constexpr auto operator\"\"_l;  /// long\n    constexpr auto operator\"\"_ll; /// long long\n    constexpr auto operator\"\"_u;  /// unsigned\n    constexpr auto operator\"\"_uc; /// unsigned char\n    constexpr auto operator\"\"_us; /// unsigned short\n    constexpr auto operator\"\"_ul; /// unsigned long\n    constexpr auto operator\"\"_f;  /// float\n    constexpr auto operator\"\"_d;  /// double\n    constexpr auto operator\"\"_ld; /// long double\n\n    /**\n     * Represents dynamic values\n     * @example _i(42), _f(42.)\n     */\n    constexpr auto _b(bool);\n    constexpr auto _c(char);\n    constexpr auto _s(short);\n    constexpr auto _i(int);\n    constexpr auto _l(long);\n    constexpr auto _ll(long long);\n    constexpr auto _u(unsigned);\n    constexpr auto _uc(unsigned char);\n    constexpr auto _us(unsigned short);\n    constexpr auto _ul(unsigned long);\n    constexpr auto _f(float);\n    constexpr auto _d(double);\n    constexpr auto _ld(long double);\n\n    /**\n     * Logical representation of constant boolean (true) value\n     * @example \"is set\"_b     : true\n     *          not \"is set\"_b : false\n     */\n    constexpr auto operator \"\"_b;\n  } // namespace literals\n\n  inline namespace operators {\n    /**\n     * Comparison functions to be used in expressions\n     * @example eq(42, 42), neq(1, 2)\n     */\n    constexpr auto eq(Operator lhs, Operator rhs);  /// ==\n    constexpr auto neq(Operator lhs, Operator rhs); /// !=\n    constexpr auto gt(Operator lhs, Operator rhs);  /// \u003e\n    constexpr auto ge(Operator lhs, Operator rhs);  /// \u003e=\n    constexpr auto lt(Operator lhs, Operator rhs);  /// \u003c\n    constexpr auto le(Operator lhs, Operator rhs);  /// \u003c=\n\n    /**\n     * Overloaded comparison operators to be used in expressions\n     * @example (42_i != 0)\n     */\n    constexpr auto operator==;\n    constexpr auto operator!=;\n    constexpr auto operator\u003e;\n    constexpr auto operator\u003e=;\n    constexpr auto operator\u003c;\n    constexpr auto operator\u003c=;\n\n    /**\n     * Overloaded logic operators to be used in expressions\n     * @example (42_i != 0 and 1 == 2_i)\n     */\n    constexpr auto operator and;\n    constexpr auto operator or;\n    constexpr auto operator not;\n\n    /**\n     * Executes parameterized tests\n     * @example \"parameterized\"_test = [](auto arg) {} | std::tuple{1, 2, 3};\n     */\n    constexpr auto operator|;\n\n    /**\n     * Creates tags\n     * @example tag(\"slow\") / tag(\"nightly\") / \"perf\"_test = []{};\n     */\n    constexpr auto operator/;\n\n    /**\n     * Creates a `fatal_assertion` from an expression\n     * @example (42_i == 0) \u003e\u003e fatal\n     */\n    constexpr auto operator\u003e\u003e;\n  } // namespace operators\n\n  /**\n   * Creates skippable test object\n   * @example skip / \"don't run\"_test = [] { };\n   */\n  constexpr auto skip = tag(\"skip\");\n\n  struct {\n    /**\n     * @example log \u003c\u003c \"message!\";\n     * @param msg stringable message\n     */\n    auto\u0026 operator\u003c\u003c(Msg msg);\n  } log{};\n\n  /**\n   * Makes object mutable\n   * @example mut(object)\n   * @param t object to be mutated\n   */\n  template\u003cclass T\u003e auto mut(const T\u0026 t) -\u003e T\u0026;\n\n  /**\n   * Default execution flow policy\n   */\n  class runner {\n   public:\n    /**\n     * @example cfg\u003coverride\u003e = {\n        .filter  = \"test.section.*\",\n        .colors  = { .none = \"\" },\n        .dry__run = true\n       };\n     * @param options.filter {default: \"*\"} runs all tests which names\n                                            matches test.section.* filter\n     * @param options.colors {default: {\n                               .none = \"\\033[0m\",\n                               .pass = \"\\033[32m\",\n                               .fail  = \"\\033[31m\"\n              } if specified then overrides default color values\n     * @param options.dry_run {default: false} if true then print test names to be\n                                               executed without running them\n     */\n    auto operator=(options);\n\n    /**\n     * @example suite _ = [] {};\n     * @param suite() executes suite\n     */\n    template\u003cclass TSuite\u003e\n    auto on(ut::events::suite\u003cTSuite\u003e);\n\n    /**\n     * @example \"name\"_test = [] {};\n     * @param test.type [\"test\", \"given\", \"when\", \"then\"]\n     * @param test.name \"name\"\n     * @param test.arg parameterized argument\n     * @param test() executes test\n     */\n    template\u003cclass... Ts\u003e\n    auto on(ut::events::test\u003cTs...\u003e);\n\n    /**\n     * @example skip / \"don't run\"_test = []{};\n     * @param skip.type [\"test\", \"given\", \"when\", \"then\"]\n     * @param skip.name \"don't run\"\n     * @param skip.arg parameterized argument\n     */\n    template\u003cclass... Ts\u003e\n    auto on(ut::events::skip\u003cTs...\u003e);\n\n    /**\n     * @example file.cpp:42: expect(42_i == 42);\n     * @param assertion.expr 42_i == 42\n     * @param assertion.location { \"file.cpp\", 42 }\n     * @return true if expr passes, false otherwise\n     */\n    template \u003cclass TExpr\u003e\n    auto on(ut::events::assertion\u003cTExpr\u003e) -\u003e bool;\n\n    /**\n     * @example expect((2_i == 1) \u003e\u003e fatal)\n     * @note triggered by `fatal`\n     *       should std::exit\n     */\n    auto on(ut::events::fatal_assertion);\n\n    /**\n     * @example log \u003c\u003c \"message\"\n     * @param log.msg \"message\"\n     */\n    template\u003cclass TMsg\u003e\n    auto on(ut::events::log\u003cTMsg\u003e);\n\n    /**\n     * Explicitly runs registered test suites\n     * If not called directly test suites are executed with run's destructor\n     * @example return run({.report_errors = true})\n     * @param run_cfg.report_errors {default: false} if true it prints the summary after running\n     */\n    auto run(run_cfg);\n\n    /**\n     * Runs registered test suites if they haven't been explicitly executed already\n     */\n    ~run();\n  };\n\n  /**\n   * Default reporter policy\n   */\n  class reporter {\n   public:\n    /**\n     * @example file.cpp:42: \"name\"_test = [] {};\n     * @param test_begin.type [\"test\", \"given\", \"when\", \"then\"]\n     * @param test_begin.name \"name\"\n     * @param test_begin.location { \"file.cpp\", 42 }\n     */\n    auto on(ut::events::test_begin) -\u003e void;\n\n    /**\n     * @example \"name\"_test = [] {};\n     * @param test_run.type [\"test\", \"given\", \"when\", \"then\"]\n     * @param test_run.name \"name\"\n     */\n    auto on(ut::events::test_run) -\u003e void;\n\n    /**\n     * @example \"name\"_test = [] {};\n     * @param test_skip.type [\"test\", \"given\", \"when\", \"then\"]\n     * @param test_skip.name \"name\"\n     */\n    auto on(ut::events::test_skip) -\u003e void;\n\n    /**\n     * @example \"name\"_test = [] {};\n     * @param test_end.type [\"test\", \"given\", \"when\", \"then\"]\n     * @param test_end.name \"name\"\n     */\n    auto on(ut::events::test_end) -\u003e void;\n\n    /**\n     * @example log \u003c\u003c \"message\"\n     * @param log.msg \"message\"\n     */\n    template\u003cclass TMsg\u003e\n    auto on(ut::events::log\u003cTMsg\u003e) -\u003e void;\n\n    /**\n     * @example file.cpp:42: expect(42_i == 42);\n     * @param assertion_pass.expr 42_i == 42\n     * @param assertion_pass.location { \"file.cpp\", 42 }\n     */\n    template \u003cclass TExpr\u003e\n    auto on(ut::events::assertion_pass\u003cTExpr\u003e) -\u003e void;\n\n    /**\n     * @example file.cpp:42: expect(42_i != 42);\n     * @param assertion_fail.expr 42_i != 42\n     * @param assertion_fail.location { \"file.cpp\", 42 }\n     */\n    template \u003cclass TExpr\u003e\n    auto on(ut::events::assertion_fail\u003cTExpr\u003e) -\u003e void;\n\n    /**\n     * @example expect((2_i == 1) \u003e\u003e fatal)\n     * @note triggered by `fatal`\n     *       should std::exit\n     */\n    auto on(ut::events::fatal_assertion) -\u003e void;\n\n    /**\n     * @example \"exception\"_test = [] { throw std::runtime_error{\"\"}; };\n     */\n    auto on(ut::events::exception) -\u003e void;\n\n    /**\n     * @note triggered on destruction of runner\n     */\n    auto on(ut::events::summary) -\u003e void;\n  };\n\n  /**\n   * Used to override default running policy\n   * @example template \u003c\u003e auto cfg\u003coverride\u003e = runner\u003creporter\u003e{};\n   */\n  struct override {};\n\n  /**\n   * Default UT execution policy\n   * Can be overwritten with override\n   */\n  template \u003cclass = override\u003e auto cfg = runner\u003creporter\u003e{};\n}\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Configuration\u003c/summary\u003e\n\u003cp\u003e\n\n| Option | Description | Example |\n|-|-|-|\n| `BOOST_UT_VERSION`        | Current version | `2'3'1` |\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"faq\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003eFAQ\u003c/summary\u003e\n\u003cp\u003e\n\n\u003ca name=\"how-it-works\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;How does it work?\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e `suite`\n\n  ```cpp\n  /**\n   * Represents suite object\n   * @example suite _ = []{};\n   */\n  struct suite final {\n    /**\n     * Assigns and executes test suite\n     */\n    [[nodiscard]] constexpr explicit(false) suite(Suite suite) {\n      suite();\n    }\n  };\n  ```\n\n\u003e `test`\n\n  ```cpp\n  /**\n   * Creates named test object\n   * @example \"hello world\"_test\n   * @return test object\n   */\n  [[nodiscard]] constexpr Test operator \"\"_test(const char* name, std::size_t size) {\n    return test{{name, size}};\n  }\n  ```\n\n  ```cpp\n  /**\n   * Represents test object\n   */\n  struct test final {\n    std::string_view name{}; /// test case name\n\n    /**\n     * Assigns and executes test function\n     * @param test function\n     */\n    constexpr auto operator=(const Test\u0026 test) {\n      std::cout \u003c\u003c \"Running... \" \u003c\u003c name \u003c\u003c '\\n';\n      test();\n    }\n  };\n  ```\n\n\u003e `expect`\n\n  ```cpp\n  /**\n   * Evaluates an expression\n   * @example expect(42_i == 42);\n   * @param expr expression to be evaluated\n   * @param source location https://en.cppreference.com/w/cpp/utility/source_location\n   * @return stream\n   */\n  constexpr OStream\u0026 expect(\n    Expression expr,\n    const std::source_location\u0026 location = std::source_location::current()\n  ) {\n    if (not static_cast\u003cbool\u003e(expr) {\n      std::cerr \u003c\u003c location.file()\n                \u003c\u003c ':'\n                \u003c\u003c location.line()\n                \u003c\u003c \":FAILED: \"\n                \u003c\u003c expr\n                \u003c\u003c '\\n';\n    }\n\n    return std::cerr;\n  }\n  ```\n\n  ```cpp\n  /**\n   * Creates constant object for which operators can be overloaded\n   * @example 42_i\n   * @return integral constant object\n   */\n  template \u003cchar... Cs\u003e\n  [[nodiscard]] constexpr Operator operator\"\"_i() -\u003e integral_constant\u003cint, value\u003cCs...\u003e\u003e;\n  ```\n\n  ```cpp\n  /**\n   * Overloads comparison if at least one of {lhs, rhs} is an Operator\n   * @example (42_i == 42)\n   * @param lhs Left-hand side operator\n   * @param rhs Right-hand side operator\n   * @return Comparison object\n   */\n  [[nodiscard]] constexpr auto operator==(Operator lhs, Operator rhs) {\n    return eq{lhs, rhs};\n  }\n  ```\n\n  ```cpp\n  /**\n   * Comparison Operator\n   */\n  template \u003cOperator TLhs, Operator TRhs\u003e\n  struct eq final {\n    TLhs lhs{}; // Left-hand side operator\n    TRhs rhs{}; // Right-hand side operator\n\n    /**\n     * Performs comparison operation\n     * @return true if expression is successful\n     */\n    [[nodiscard]] constexpr explicit operator bool() const {\n      return lhs == rhs;\n    }\n\n    /**\n     * Nicely prints the operation\n     */\n    friend auto operator\u003c\u003c(OStream\u0026 os, const eq\u0026 op) -\u003e Ostream\u0026 {\n      return (os \u003c\u003c op.lhs \u003c\u003c \" == \" \u003c\u003c op.rhs);\n    }\n  };\n  ```\n\n\u003e `Sections`\n\n  ```cpp\n  /**\n   * Convenient aliases for creating test named object\n   * @example should(\"return true\") = [] {};\n   */\n  constexpr auto should = [](const auto name) { return test{name}; };\n  ```\n\n\u003e `Behaviour Driven Development (BDD)`\n\n  ```cpp\n  /**\n   * Convenient aliases for creating BDD tests\n   * @example feature(\"Feature\") = [] {};\n   * @example scenario(\"Scenario\") = [] {};\n   * @example given(\"I have an object\") = [] {};\n   * @example when(\"I call it\") = [] {};\n   * @example then(\"I should get\") = [] {};\n   */\n  constexpr auto feature  = [](const auto name) { return test{name}; };\n  constexpr auto scenario = [](const auto name) { return test{name}; };\n  constexpr auto given    = [](const auto name) { return test{name}; };\n  constexpr auto when     = [](const auto name) { return test{name}; };\n  constexpr auto then     = [](const auto name) { return test{name}; };\n  ```\n\n\u003e https://godbolt.org/z/6Nk5Mi\n\n\u003e `Spec`\n\n  ```cpp\n  /**\n   * Convenient aliases for creating Spec tests\n   * @example describe(\"test\") = [] {};\n   * @example it(\"should...\") = [] {};\n   */\n  constexpr auto describe = [](const auto name) { return test{name}; };\n  constexpr auto it       = [](const auto name) { return test{name}; };\n  ```\n\n\u003e [Example implementation](https://github.com/boost-ext/ut/tree/gh-pages/denver-cpp-2020/example)\n\n\u003e Try it online\n\n* Header - https://godbolt.org/z/x96n8b\n* Module - https://wandbox.org/permlink/LrV7WwIgghTP1nrs\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"fast-compilation-times\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Fast compilation times \u003ca href=\"#benchmarks\"\u003e(Benchmarks)\u003c/a\u003e?\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e Implementation\n\n* Leveraging [C++20](#cpp-20) features\n\n* Avoiding unique types for lambda expressions\n\n```cpp\n  template \u003cclass Test\u003e\n    requires not std::convertible_to\u003cTest, void (*)()\u003e\u003e\n  constexpr auto operator=(Test test);\n\nvs\n\n  // Compiles 5x faster because it doesn't introduce a new type for each lambda\n  constexpr auto operator=(void (*test)());\n```\n\n* `Type-name` erasure (allows types/function memoization)\n\n```cpp\n  eq\u003cintegral_constant\u003c42\u003e, int\u003e{ {}, 42 }\n\nvs\n\n  // Can be memoized - faster to compile\n  eq\u003cint, int\u003e{42, 42}\n```\n\n* Limiting preprocessor work\n  * Single header/module\n  * Minimal number of include files\n\n* Simplified versions of\n  * `std::function`\n  * `std::string_view`\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"cpp-20\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;C++20 features?\u003c/summary\u003e\n\u003cp\u003e\n\n* API\n\n  * [Source Location](https://eel.is/c++draft/support.srcloc#source.location.syn)\n    * Assertions - `expect(false)` - ` __FILE__:__LINE__:FAILED [false]`\n\n  * [Designated initializers](https://eel.is/c++draft/dcl.init#nt:designated-initializer-list)\n    * Configuration - `cfg\u003coverride\u003e = {.filter = \"test\"}`\n\n  * [Non-Type Template Parameter](https://eel.is/c++draft/temp.arg.nontype)\n    * Constant matchers - `constant\u003c42_i == 42\u003e`\n\n  * [Template Parameter List for generic lambdas](https://eel.is/c++draft/expr.prim.lambda)\n    * Parameterized tests - `\"types\"_test = []\u003cclass T\u003e() {};`\n\n  * [Concepts](https://eel.is/c++draft/concepts.lang)\n    * Operators - `Operator @ Operator`\n\n  * [Modules](https://eel.is/c++draft/module)\n    * `import boost.ut;`\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"cpp-2x\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;C++2X integration?\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e Parameterized tests with Expansion statements (https://wg21.link/P1306r1)\n\n```cpp\ntemplate for (auto arg : std::tuple\u003cint, double\u003e{}) {\n  test(\"types \" + std::to_string(arg)) = [arg] {\n    expect(type(arg) == type\u003cint\u003e or type(arg) == type\u003cdouble\u003e);\n  };\n}\n```\n\n```\nAll tests passed (2 asserts in 2 tests)\n```\n\n\u003e https://cppx.godbolt.org/z/dMmqmM\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"std\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Is standardization an option?\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e Personally, I believe that C++ standard could benefit from common testing primitives (`expect`, `\"\"_test`) because\n\n* It lowers the entry-level to the language (no need for third-party libraries)\n* It improves the education aspect (one standard way of doing it)\n* It makes the language more coherent/stable (consistent design with other features, stable API)\n* It makes the testing a first class citizen (shows that the community cares about this aspect of the language)\n* It allows to publish tests for the Standard Library (STL) in the standard way (coherency, easier to extend)\n* It allows to act as additional documentation as a way to verify whether a particular implementation is conforming (quality, self-verification)\n* It helps with establishing standard vocabulary for testing (common across STL and other projects)\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"macros\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Can I still use macros?\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e Sure, although please notice that there are negatives of using macros such as\n\n* Error messages might be not clear and/or point to the wrong line\n* Global scope will be polluted\n* Type safety will be ignored\n\n```cpp\n#define EXPECT(...) ::boost::ut::expect(::boost::ut::that % __VA_ARGS__)\n#define SUITE       ::boost::ut::suite _ = []\n#define TEST(name)  ::boost::ut::detail::test{\"test\", name} = [=]() mutable\n\nSUITE {\n  TEST(\"suite\") {\n    EXPECT(42 == 42);\n  };\n};\n\nint main() {\n  TEST(\"macro\") {\n    EXPECT(1 != 2);\n  };\n\n  TEST(\"vector\") {\n    std::vector\u003cint\u003e v(5);\n\n   EXPECT(fatal(5u == std::size(v))) \u003c\u003c \"fatal\";\n\n    TEST(\"resize bigger\") {\n      v.resize(10);\n      EXPECT(10u == std::size(v));\n    };\n  };\n}\n```\n\n```\nAll tests passed (4 asserts in 3 tests)\n```\n\n\u003e https://godbolt.org/z/WcEKTr\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;What about Mocks/Stubs/Fakes?\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e Consider using one of the following frameworks\n\n* https://github.com/cpp-testing/GUnit/blob/master/docs/GMock.md\n* https://github.com/eranpeer/FakeIt\n* https://github.com/dascandy/hippomocks\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;What about Microbenchmarking?\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e [Example benchmark](example/benchmark.cpp)\n\n\u003e Consider using one of the following frameworks\n\n* https://github.com/google/benchmark\n* https://github.com/DigitalInBlue/Celero\n* https://github.com/libnonius/nonius\n* https://github.com/martinus/nanobench\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Related materials/talks?\u003c/summary\u003e\n\u003cp\u003e\n\n* [[Boost].UT - Unit Testing Framework - Kris Jusiak](https://boost-ext.github.io/ut/denver-cpp-2019)\n* [Future of Testing with C++20 - Kris Jusiak](https://boost-ext.github.io/ut/meeting-cpp-2020)\n* [Macro-Free Testing with C++20 - Kris Jusiak](https://www.youtube.com/watch?v=irdgFyxOs_Y)\n* [\"If you liked it then you `\"should have put a\"_test` on it\", Beyonce rule - Kris Jusiak](https://www.youtube.com/watch?v=yCI8MjvOMeE)\n* [Principles of Unit Testing With C++ - Dave Steffen and Kris Jusiak](https://www.youtube.com/watch?v=oOcuJdJJ33g)\n* [Empirical Unit Testing - Dave Steffen](https://www.twitch.tv/videos/686512433)\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"how-to-contribute\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;How to contribute?\u003c/summary\u003e\n\u003cp\u003e\n\n\u003e [CONTRIBUTING](.github/CONTRIBUTING.md)\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003ca name=\"benchmarks\"\u003e\u003c/a\u003e\n\u003cdetails open\u003e\u003csummary\u003eBenchmarks\u003c/summary\u003e\n\u003cp\u003e\n\n| Framework | Version | Standard | License | Linkage | Test configuration |\n|-|-|-|-|-|-|\n| [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` |\n| [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` |\n| [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` |\n| [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` |\n| [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 | |\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd colspan=\"3\" align=\"center\"\u003e\n    \u003ca href=\"https://github.com/cpp-testing/ut-benchmark/tree/master/benchmarks\"\u003e\u003cb\u003eInclude\u003c/b\u003e\u003c/a\u003e / \u003ci\u003e0 tests, 0 asserts, 1 cpp file\u003c/i\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd colspan=\"3\" align=\"center\"\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_include.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_include.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\n  \u003ctr\u003e\n    \u003ctd colspan=\"3\" align=\"center\"\u003e\n    \u003ca href=\"https://github.com/cpp-testing/ut-benchmark/tree/master/benchmarks\"\u003e\u003cb\u003eAssert\u003c/b\u003e\u003c/a\u003e / \u003ci\u003e1 test, 1'000'000 asserts, 1 cpp file\u003c/i\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_assert.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_assert.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_assert.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_assert.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_assert.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_assert.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\n  \u003ctr\u003e\n    \u003ctd colspan=\"3\" align=\"center\"\u003e\n    \u003ca href=\"https://github.com/cpp-testing/ut-benchmark/tree/master/benchmarks\"\u003e\u003cb\u003eTest\u003c/b\u003e\u003c/a\u003e / \u003ci\u003e1'000 tests, 0 asserts, 1 cpp file\u003c/i\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_test.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_test.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_test.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_test.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_test.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_test.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\n  \u003ctr\u003e\n    \u003ctd colspan=\"3\" align=\"center\"\u003e\n    \u003ca href=\"https://github.com/cpp-testing/ut-benchmark/tree/master/benchmarks\"\u003e\u003cb\u003eSuite\u003c/b\u003e\u003c/a\u003e / \u003ci\u003e10'000 tests, 0 asserts, 100 cpp files\u003c/i\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_suite.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_suite.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_suite.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_suite.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_suite.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_suite.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\n  \u003ctr\u003e\n    \u003ctd colspan=\"3\" align=\"center\"\u003e\n    \u003ca href=\"https://github.com/cpp-testing/ut-benchmark/tree/master/benchmarks\"\u003e\u003cb\u003eSuite+Assert\u003c/b\u003e\u003c/a\u003e / \u003ci\u003e10'000 tests, 40'000 asserts, 100 cpp files\u003c/i\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_suite+assert.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_suite+assert.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_suite+assert.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_suite+assert.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_suite+assert.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_suite+assert.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\n  \u003ctr\u003e\n    \u003ctd colspan=\"3\" align=\"center\"\u003e\n    \u003ca href=\"https://github.com/cpp-testing/ut-benchmark/tree/master/benchmarks\"\u003e\u003cb\u003eSuite+Assert+STL\u003c/b\u003e\u003c/a\u003e / \u003ci\u003e10'000 tests, 20'000 asserts, 100 cpp files\u003c/i\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_suite+assert+stl.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_suite+assert+stl.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_suite+assert+stl.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_suite+assert+stl.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_suite+assert+stl.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_suite+assert+stl.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\n  \u003ctr\u003e\n    \u003ctd colspan=\"3\" align=\"center\"\u003e\n    \u003ca href=\"https://github.com/cpp-testing/ut-benchmark/tree/master/benchmarks\"\u003e\u003cb\u003eIncremental Build - Suite+Assert+STL\u003c/b\u003e\u003c/a\u003e / \u003ci\u003e1 cpp file change (1'000 tests, 20'000 asserts, 100 cpp files)\u003c/i\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_incremental.suite+assert+stl.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Compilation_incremental.suite+assert+stl.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_incremental.suite+assert+stl.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/Execution_incremental.suite+assert+stl.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_incremental.suite+assert+stl.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/BinarySize_incremental.suite+assert+stl.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\n  \u003ctr\u003e\n    \u003ctd colspan=\"3\" align=\"center\"\u003e\n    \u003ca href=\"https://github.com/cpp-testing/ut-benchmark/tree/master/benchmarks\"\u003e\u003cb\u003eSuite+Assert+STL\u003c/b\u003e\u003c/a\u003e / \u003ci\u003e10'000 tests, 20'000 asserts, 100 cpp files\u003cbr/\u003e(Headers vs Precompiled headers vs C++20 Modules)\u003c/i\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/ut_Compilation_suite+assert+stl.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/ut_Compilation_suite+assert+stl.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/ut_Execution_suite+assert+stl.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/ut_Execution_suite+assert+stl.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003ca href=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/ut_BinarySize_suite+assert+stl.png\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/cpp-testing/ut-benchmark/master/results/ut_BinarySize_suite+assert+stl.png\"\u003e\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003e https://github.com/cpp-testing/ut-benchmark\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n---\n\n**Disclaimer** `UT` is not an official Boost library.\n","funding_links":[],"categories":["C++","Testing Frameworks","测试框架"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboost-ext%2Fut","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fboost-ext%2Fut","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboost-ext%2Fut/lists"}