Although there are already libraries for unit testing C ++ code, for example, Google Test or Bandit , but they are not written by me here, in my opinion, it is somehow over complicated, compared to the same JS. There you just do, for example, npm i mocha assert --save-dev
and you can start writing tests, but here you need to do it with pens, and in the case of gtest
also build it with cmake
. Bandit connects easily, but does not know how to serialize the results into some kind of data format, gtest
does, but needs to be collected separately. And I do not want to choose "either this or that." I needed to make a convenient and simple tool for my tasks. I wanted to get a simple library without dependencies, header-only, into several files, which can be easily and quickly connected to my project, it is convenient to make changes to it (if necessary). But, most importantly, I wanted to get convenient, machine-readable reports, and not only in stdout
(or xml
, as in gtest
), but also in any other format that I want. Further under the cut.
As I wrote above, the dock header-only library, which means its connection is as simple as possible:
#include <iostream> #include <dock/dock.hpp> using namespace dock; int main() { core().run(); return 0; }
When building, for example, in gcc, you need to transfer only the path to the folder with libraries and specify the standard of the C ++ 14 language. I deliberately do this, because I am writing new projects in a fresh standard, and there are already my own libraries to support the old ones.
The description of the tests is also made extremely simple:
using namespace dock; Module(u8"Some module 1", [](DOCK_MODULE()) { Test(u8"Some test 1", []() { uint8_t value = 0x10; uint8_t expectedValue = 0x10; Assert::isEqual(value, expectedValue); }); Test(u8"Some test 2", []() { uint8_t value = 0x10; uint8_t expectedBorder = 0x20; Assert::isLess(value, expectedBorder); }); }); Module(u8"Some module 2", [](DOCK_MODULE()) { Test(u8"Some test 1", []() { Assert::isTrue(true); }); Test(u8"Some test 2", []() { Assert::isTrue(false); }); });
For convenience, tests are grouped into modules. They pass an object std::function<void(Module*)>
, inside which tests are described directly. Tests have approximately the same syntax, only a functional object without parameters. So far, I have not done a check on the uniqueness of the module or test name, because it was not critical.
The Assert
library contains a simple set of isTrue
, isEquals
, isGreater
, isLess
, which by default can compare objects through the operators ==
, >
or <
. If there are no operators, then the comparison function can be passed at the end with a parameter (for example, in the form of a lambda).
static void isTrue(std::function<bool()> fcn); template<typename T> static void isEqual(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>);
And now just what I needed: a convenient conversion of test results into the required data format. For a start, I just want to work with the statistics of the project, look at the dynamics of the tests and similar things, and it is convenient for me to do it on JS. Therefore, the first format I needed is JSON. There are already three ready serializers in the repository: in JSON, in plain text and output to the console with backlighting. Using serializers is very simple:
nlohmann::json outJson; JsonSerializer serializer(outJson, 4); core().run(); core().collect(serializer); std::cout << serializer << std::endl;
And the serializer interface itself looks like this:
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); };
Those. we can output the result anywhere, only substitute std::ostream
and that's it. The logic of the serializer is as follows:
collect()
and it calls the serialize()
method with the result vector.<<
operator, the toString()
method is called, which std::ostream
string in std::ostream
.serialize()
, we immediately create the necessary string, and then either simply return it, or save the reference to the results and generate the output immediately when issued to ostream. In any case, there is freedom of movement - the engine simply issues std::vector<dock::Result>
, and what to do with it is your business :).The license is free (MIT), because I do not mind and would be pleased to see its use. The termcolor and JSON for Modern C ++ libraries were used for serializers, but you can safely remove them along with unnecessary serializers.
Source: https://habr.com/ru/post/319858/
All Articles