dock: проста бібліотека модульного тестування коду на С++

Хоча і існують вже бібліотеки для юніт-тестування коду на С++, наприклад, Google Test або Bandit, а вони написані не мною тут воно, на мій погляд, якось переусложнено, порівняно з тим же JS. Там просто робиш, наприклад,
npm i mocha assert --save-dev
і можна приступати до написання тестів, а тут же треба це зробити ручками, а у випадку з
gtest
ще й зібрати з допомогою
cmake
. Bandit підключається просто, але не вміє в серіалізацію результатів у якійсь формат даних,
gtest
це вміє, але його потрібно збирати окремо. А я не хочу вибирати "або те, або це". Мені було потрібно зробити зручний і простий інструмент під мої завдання. Я хотів отримати просту бібліотеку без залежностей, header-only, на кілька файлів, яку можна легко і швидко підключити до свого проекту, зручно внести в неї зміни, якщо це буде необхідно). Але, найголовніше, мені хотілося отримувати зручні, машиночитані звіти, причому не тільки в
stdout
або
xml
, як в
gtest
), але і в будь-який інший формат, який я захочу. Далі під катом.
Як я вже писав вище, бібліотека dock header-only, а значить її підключення максимально просте:
#include < iostream>
#include <dock/dock.hpp>

using namespace dock;

int main() {
core().run();

return 0;
}

При складанні, наприклад, у gcc, потрібно передати тільки шлях до папки з бібліотеками та вказати стандарт мови C++14. Я навмисно роблю так тому, що нові проекти я пишу на свіжому стандарті, а для підтримки старих є вже готові бібліотеки.
Опис тестів теж зроблено гранично простим:
using namespace dock;

Module(u8"Some module 1", [](DOCK_MODULE()) {
Test(u8"Some test 1", []() {
Assert::isTrue([]() -> bool { return true; });
});

Test(u8"Some test 2", []() {
Assert::isTrue([]() -> bool { return true; });
});
});

Module(u8"Some module 2", [](DOCK_MODULE()) {
Test(u8"Some test 1", []() {
Assert::isTrue([]() -> bool { return true; });
});

Test(u8"Some test 2", []() {
Assert::isTrue([]() -> bool { return false; });
});
});

Для зручності тести групуються в модулі. В них передається об'єкт
std::function<void(Module*)>
, усередині якого описуються безпосередньо тести. Тести мають приблизно такий же синтаксис, тільки функціональний об'єкт без параметрів. Поки що я не робив перевірку на унікальність імені модуля або тесту, тому що це було не критично.
"Бібліотека"
Assert
містить простий набір методів
isTrue
,
isEquals
,
isGreater
,
isLess
, котрі можуть порівнювати об'єкти через оператори
==
,
>
або
<
. Якщо операторів немає, то можна функцію порівняння передати в кінці параметром (наприклад, у вигляді лямбды).
static void isTrue(std::function<bool()> fcn);

template < typename T>
static void isEquals(const T a, const T b, std::function<bool(const T, const T)> compareFcn = defaultEqualsFunction<T>);

template < typename T>
static void isGreater(const T a, const T b, std::function<bool(const T, const T)> compareFcn = defaultGreaterFunction<T>);

template < typename T>
static void isLess(const T a, const T b, std::function<bool(const T, const T)> compareFcn = defaultLessFunction<T>);

А тепер як раз те, що було мені потрібно: зручне перетворення результатів тестування в необхідний формат даних. Для початку, просто хочеться попрацювати з статистикою ведення проекту, дивитися динаміку за тестами і подібні речі, і мені це зручно робити на JS. Тому перший формат, який мені потрібен — JSON. В репозиторії є вже три готових сериализатора: в JSON, в plain text і вивід на консоль з підсвічуванням. Використання сериализаторов дуже просте:
nlohmann::json outJson;
JsonSerializer serializer(outJson, 4);

core().run();
core().collect(serializer);

std::cout << serializer << std::endl;

А сам інтерфейс сериализатора виглядає наступним чином:
class ResultSerializer {
public:
virtual ~ResultSerializer() = default;

virtual void serialize(std::vector<Result>& results) = 0;
virtual std::string toString() const = 0;
friend std::ostream& operator<<(std::ostream& os, ResultSerializer& s);
};

тобто виводити результат можемо куди завгодно, тільки підставити
std::ostream
і все. Логіка роботи сериализатора наступна:
  • Передаємо сериализатор движку через
    collect()
    та він викликає метод
    serialize()
    з вектором результатів.
  • В операторі
    <<
    викликається метод
    toString()
    , який видає рядок
    std::ostream
    .
    Можна зробити два варіанти: або при виклику
    serialize()
    відразу створюємо потрібний рядок, а потім її або просто повертаємо, або зберігаємо посилання на результати і генеруємо видачу безпосередньо при видачі в ostream. У будь-якому випадку, залишається свобода руху — движок видає просто
    std::vector<dock::Result>
    , а що з ним робити вже справа ваша :).
Ліцензія вільна (MIT), тому що мені не шкода і буде приємно бачити її використання. Для сериализаторов використовувалися бібліотеки termcolor і JSON for Modern C++, але можна спокійно прибрати їх разом з непотрібними сериализаторами.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.