Хотя и существуют уже библиотеки для юнит-тестирования кода на С++, например, 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++, но можно спокойно убрать их вместе с ненужными сериализаторами.
Автор: emdc