μt¹ – небольшая (~100 Кб), header-only (единственный файл ut.hpp
) C++20 библиотека модульного тестирования.
В отличие от большинства аналогов (сatch³, doctest⁴, etc.) основана не на макросах, а на возможностях стандарта C++20.
Библиотекой поддерживаются техники TDD (разработка через тестирование), BDD (разработка через поведение) и язык BDD Gherkin.
Зависит только от std
.
Примеры:
#include <boost/ut.hpp> // import boost.ut;
constexpr auto sum(auto... values) { return (values + ...); }
int main() {
using namespace boost::ut;
"sum"_test = [] {
expect(sum(0) == 0_i);
expect(sum(1, 2) == 3_i);
expect(sum(1, 2) > 0_i and 41_i == sum(40, 2));
};
}
#include <boost/ut.hpp>
#include <chrono>
#include <iostream>
#include <string>
#include <string_view>
namespace benchmark {
struct benchmark : boost::ut::detail::test {
explicit benchmark(std::string_view _name)
: boost::ut::detail::test{"benchmark", _name} {}
template <class Test>
auto operator=(Test _test) {
static_cast<boost::ut::detail::test&>(*this) = [&_test, this] {
const auto start = std::chrono::high_resolution_clock::now();
_test();
const auto stop = std::chrono::high_resolution_clock::now();
const auto ns =
std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start);
std::clog << "[" << name << "] " << ns.count() << " ns\n";
};
}
};
[[nodiscard]] auto operator""_benchmark(const char* _name,
decltype(sizeof("")) size) {
return ::benchmark::benchmark{{_name, size}};
}
#if defined(__GNUC__) or defined(__clang__)
template <class T>
void do_not_optimize(T&& t) {
asm volatile("" ::"m"(t) : "memory");
}
#else
#pragma optimize("", off)
template <class T>
void do_not_optimize(T&& t) {
reinterpret_cast<char volatile&>(t) =
reinterpret_cast<char const volatile&>(t);
}
#pragma optimize("", on)
#endif
} // namespace benchmark
int main() {
using namespace boost::ut;
using namespace benchmark;
"string creation"_benchmark = [] {
std::string created_string{"hello"};
do_not_optimize(created_string);
};
}
BDD:
#include <boost/ut.hpp>
int main() {
using namespace boost::ut::literals;
using namespace boost::ut::operators::terse;
using namespace boost::ut::bdd;
"Scenario"_test = [] {
given("I have...") = [] {
when("I run...") = [] {
then("I should have...") = [] { 1_u == 1u; };
then("I should have...") = [] { 1u == 1_u; };
};
};
};
feature("Calculator") = [] {
scenario("Addition") = [] {
given("I have number 40") = [] {
auto number = 40;
when("I add 2 to number") = [&number] { number += 2; };
then("I expect number to be 42") = [&number] { 42_i == number; };
};
};
};
// clang-format off
scenario("Addition");
given("I have number 40");
auto number = 40;
when("I add 2 to number");
number += 2;
then("I expect number to be 42");
42_i == number;
}
Gherkin:
#include <boost/ut.hpp>
#include <fstream>
#include <numeric>
#include <streambuf>
#include <string>
template <class T>
class calculator {
public:
auto enter(const T& value) -> void { values_.push_back(value); }
auto add() -> void {
result_ = std::accumulate(std::cbegin(values_), std::cend(values_), T{});
}
auto sub() -> void {
result_ = std::accumulate(std::cbegin(values_) + 1, std::cend(values_),
values_.front(), std::minus{});
}
auto get() const -> T { return result_; }
private:
std::vector<T> values_{};
T result_{};
};
int main(int argc, const char** argv) {
using namespace boost::ut;
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Calculator") = [&] {
steps.scenario("*") = [&] {
steps.given("I have calculator") = [&] {
calculator<int> calc{};
steps.when("I enter {value}") = [&](int value) { calc.enter(value); };
steps.when("I press add") = [&] { calc.add(); };
steps.when("I press sub") = [&] { calc.sub(); };
steps.then("I expect {value}") = [&](int result) {
expect(that % calc.get() == result);
};
};
};
};
};
// clang-format off
"Calculator"_test = steps |
R"(
Feature: Calculator
Scenario: Addition
Given I have calculator
When I enter 40
When I enter 2
When I press add
Then I expect 42
Scenario: Subtraction
Given I have calculator
When I enter 4
When I enter 2
When I press sub
Then I expect 2
)";
// clang-format on
if (argc == 2) {
const auto file = [](const auto path) {
std::ifstream file{path};
return std::string{(std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>()};
};
"Calculator"_test = steps | file(argv[1]);
}
}
- https://github.com/boost-ext/ut
- https://boost-ext.github.io/ut – примеры, документация, бенчмарки
- https://github.com/catchorg/Catch2
- https://github.com/doctest/doctest
- https://boost-ext.github.io/ut/denver-cpp-2019 – слайд-презентация.
- https://www.youtube.com/watch?v=irdgFyxOs_Y – презентация на CppCon 2020.