LINUX.ORG.RU

μt — C++20 библиотека модульного тестирования

 , , , ,


0

2

μ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]);
  }
}

  1. https://github.com/boost-ext/ut
  2. https://boost-ext.github.io/ut – примеры, документация, бенчмарки
  3. https://github.com/catchorg/Catch2
  4. https://github.com/doctest/doctest
  5. https://boost-ext.github.io/ut/denver-cpp-2019 – слайд-презентация.
  6. https://www.youtube.com/watch?v=irdgFyxOs_Y – презентация на CppCon 2020.
★★★★★

Последнее исправление: dataman (всего исправлений: 2)

Я хотел использовать, но сходу не нашел как её заставить выводить результат в формате junit xml. Это возможно?

Reset ★★★★★
()
 What about Mocks/Stubs/Fakes?
 Consider using one of the following frameworks
 https://github.com/cpp-testing/GUnit/blob/master/docs/GMock.md

Я так понял мокать оно не умеет и для замены gtest еще не годится?

Жаль, примеры с BDD неплохи.

PRN
()
Ответ на: комментарий от Reset

выводить результат в формате junit xml

«Из коробки» нет.

Это возможно?

Да, есть примеры использования cfg::runner, cfg::reporter, и cfg::printer.

dataman ★★★★★
() автор топика

Насколько сильно μt замедляет компиляцию проекта при подключении заголовочного файла? Например, Catch2 знатный тормоз, а тот же doctest особо не влияет на время сборки.

m0rph ★★★★★
()

Не раскрыта тема, чем она лучше гуглотестов и... раз уж пошла такая пьянка маркетингового минимализма - наколенных юнит-тестов, велосипедимых за 5 минут на ходу, средствами собственно сиплюсплюса :)

slackwarrior ★★★★★
()
Последнее исправление: slackwarrior (всего исправлений: 1)
Ответ на: комментарий от PRN

BDD — это прежде всего методология. Она в головах, а не в конструкциях :) Конструкции обычно отражают видение тараканов в голове автора. Это почти проективные тесты скорее авторов, как они понимают методологию (чаще всего «ритуально воспроизводя бестпрактисы»). Именно поэтому большинство «фреймворков»: а)перегружены абстрактной фигней, которая нужна 3.5 человекам из секты «истинных адептов правильных тестов» б) ограничены представлениями авторов о полезности конструкций для. 90 процентов людей использует некоторое «компактное подмножество». Особенно радуют конструкции с пересекающимся функционалом «для почти того же» (или соответствующие тому же самому в языках программирования).

А большинство любых тестов — это исходная диспозиция «дано», с предусловиями, и проверка сходимости постусловий :)

Например, когда автотестированием еще почти нигде не пахло, автоматизировали тестирование ПО для бирж на основе публично доступных исторических данных, тестерам предлагалась «оверинжиниреная» система автотестирования с конфигурацией на XML, в которой большую часть теста занимала простыня с конфигурацией... которая во многих тестах пересекается и не должна вообще копипаститься бы. Но авторы системы на это ложили болт, т.к. «они так видят», «тесты должны быть изолированы!» (поэтому восход солнца в каждом вручную), и смысловая часть теста терялась в простыне конфигурации. Долго ли коротко ли, этого монстра заменили на «пулемет» в виде эмулятора биржи для обстрела сервера сообщениями, на встроенном жабоскрипте с наколенной «библиотекой», сводящей тест к трем строчкам — сообщение, запрос, ответ. А не «сферический в вакууме манямирок» с фейками всего подряд. И даже тестеры, которые воротили нос от «работы программистов» и херачили проверки вручную по эксельке, поменяли мнение и нашли новую систему полезной.

DSL тестов же в большинстве случаев довольно тривиальный — в противном случае, когда он пытается быть слишком «обобщенным», он превращается в... еще один странный язык программирования, непротиворечивость(или хотя бы безбажность) и пользу которого как бэ могут оценить лишь «истинные адепты» (по сути каличные программисты на нем), которые, как специалисты подобные флюсу, чаще всего не слышали про теорему о неполноте и что строго что-то доказать в рамках самой системы они не могут примерно ничего (потому что теорем в процессе работы не доказывают, и доказательное программирование с формальной верификацией (с использованием хотя бы Coq) для них вообще что-то на непонятном) :)

slackwarrior ★★★★★
()
Последнее исправление: slackwarrior (всего исправлений: 1)
Ответ на: комментарий от Reset

как её заставить выводить результат в формате junit xml

Я ошибся, есть reporter_junit. Нужно добавлять --reporter junit в параметры бинарника.

$ ./boost_ut_hello_world --reporter junit

<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="all" tests="3" failures="0" time="0">
<testsuite classname="./boost_ut_hello_world" name="global" tests="3" errors="0" failures="0" skipped="0" time="0" version="201">
 <testcase classname="global" name="hello world" tests="3" errors="0" failures="0" skipped="0" time="0" status="PASSED">
  <system-out>

Running test "hello world"...
  </system-out>
 </testcase>
</testsuite>
</testsuites>

$ ./boost_ut_hello_world -h

./boost_ut_hello_world [<test name|pattern|tags> ... ] options

with options:
  -? -h --help                  display usage information
  -l --list-tests               list all/matching test cases
  -t, --list-tags               list all/matching tags
  -s, --success                 include successful tests in output
  -o, --out <filename>          output filename
  -r, --reporter <name>         reporter to use (defaults to console)
  -n, --name <name>             suite name
  -a, --abort                   abort at first failure
  -x, --abortx <no. failures>   abort after x failures
  -d, --durations               show test durations
  -D, --min-duration <seconds>  show test durations for [...]
  -f, --input-file <filename>   load test names to run from a file
  --list-test-names-only        list all/matching test cases names only
  --list-reporters              list all reporters
  --order <decl|lex|rand>       test case order (defaults to decl)
  --rng-seed <'time'|number>    set a specific seed for random numbers
  --use-colour <yes|no>         should output be colourised
  --libidentify                 report name and version according to libidentify standard
  --wait-for-keypress <never|start|exit|both>waits for a keypress before exiting

$ ./boost_ut_hello_world --list-reporters

available reporter:
  console (default)
  junit
dataman ★★★★★
() автор топика
Последнее исправление: dataman (всего исправлений: 1)
Ответ на: комментарий от slackwarrior

BDD — это прежде всего методология.

Все верно, но очевидно что забивать гвозди удобнее молотком, а не миксером.

т.к. «они так видят», «тесты должны быть изолированы!»

Это правильное утверждение, если которое воспринимать буквально будет то, что ты описал.

заменили на «пулемет» в виде эмулятора биржи для обстрела сервера сообщениями

Конфигурация эмулятора никуда из тестов не делась, могла упроститься, но исчезнуть она не могла. Иначе один тест портит сетап для другого теста. И именно поэтому они должны быть «изолированы».

…каличные…

Мне кажется тебя куда-то понесло)

…тесты …теорему о неполноте …Coq

Смешались люди, кони, паровозы)) Тебя точно понесло.

PRN
()
Последнее исправление: PRN (всего исправлений: 2)
Ответ на: комментарий от PRN

Конфигурация эмулятора никуда из тестов не делась

Делась :) Нет ее. С т.з. теста важнее суть запроса и суть отклика.

Иначе один тест портит сетап для другого теста.

Никак не портит, потому что это один и тот же эндпойнт, который не меняется за время жизни от SOD до EOD. И размазывать там манную кашу со «сценарием» и «ролями» кви-про-кво «как хакер я хочу поиметь сервер в такой позе»«как админ я хочу обломать юзеров с наступлением EOD» не надо. Нужно послать сообщение про надвигающийся EOD и получить отклик что «покайтесь, ибо грядет» OK, сервер гоинг даун райт ин тудей миднайт.

Наблюдаю с 2013-го как и зачем это вот «правильное BDD» c «Геркином и адептами» внедрялось. Да, многие девелоперы с тех пор себе кукумбер глубоко засунули, причем добровольно, не задаваясь вопросами «Why BDD sucks in practice?», но вопросы были еще тогда не только у меня

Смешались люди, кони, паровозы)) Тебя точно понесло.

Ничего не смешалось. Т.к. эти «методологии» — ребрендинг того что и так известно, и ничего нового, кроме ритуалов, не добавляет. Просто у 95% так называемых «автотестеров» на рыночке порешающем нет квалификации для формальной верификации, вот и заменяют ее этой стыдобой с «еще одной наколенной индуктивной теорией» над другой индуктивной теорией, которой является любая программа. Вместо собственно доказательств работоспособности — миллион отчетиков об автотестах, которые по сути являются программами... на которые тестов нет. Поэтому выпускают не «качественное ПО», а «это такое качество» (good enough software), не работающие программы, а «программные продукты» (ТМ)

slackwarrior ★★★★★
()
Последнее исправление: slackwarrior (всего исправлений: 1)
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.