πŸ“œ ⬆️ ⬇️

Test Data Generator for C ++

image


When unit-testing the code, sooner or later the question of test data arises. And if in one case it is enough just a few hard-wired variables, in other cases some large and random data are needed. In a managed world, there are no problems with generating custom types (take the same Autofixture), but the C ++ world often causes pain and suffering (correct me if this is not the case). Not so long ago, I became acquainted with the remarkable library boost :: di, and under its influence, I began to ripen the idea of ​​a library that would allow C ++ programmers to generate custom data types clogged with random values, and this would not require their prior description. It turned out something like:


struct dummy_member{ float a; int b; }; struct dummy{ explicit dummy(dummy_member val, std::string c) : val_(val), c_(c) {} private: dummy_member val_; std::string c_; }; int main(int argc, char* argv){ auto d = datagen::random<dummy>(); return 0; } 

β†’ Link to the code . Header-only library, C ++ 14. I ask all interested under the cat.


Main features of the library



Generating built-in types


The generation of built-in types (char, wchar_t, etc.) is naturally supported. In this case, integer types are generated simply as a set of bits, and float and double - as the sum of a random integer (int32_t and int64_t, respectively) and random values ​​in the range from -1 to 1. To generate a bool value, a comparison of two random integers is used.


 std::cout << "The answer to the question of everything is:" << datagen::random<int>() << std::endl; 

Generate custom types.


To generate custom types, the same idea was used as the basis for boost :: di (thanks to its author), namely the possibility of writing the universal type any_type, which is implicitly convertible into any other type (with rare exceptions). Adding a LITTLE template, it turned out a piece that generates custom types using the following tools:


  1. User defined generation algorithm.
  2. Generation based on a public constructor with the maximum number of parameters. Everything is as in the example at the beginning of the article ( struct dymmy ).
  3. Generation based on {} initialization. Everything is the same as in the first example ( struct dummy_member ).

To generate objects based on a user-defined procedure, the pattern must be partially or fully specialized.


 template<> struct datagen::value_generation_algorithm<TType> { TType get_random(random_source_base&); }; 

This adds the ability to take out some type generation parameters as members of this class, which in turn allows us to influence the type generation. For example, the algorithm for generating std std looks like this:


 namespace datagen{ template <class CharType, class Traits, class Allocator> struct value_generation_algorithm<std::basic_string<CharType, Traits, Allocator>>{ using string_t = std::basic_string<CharType, Traits, Allocator>; string_t get_random(random_source_base& r_source){...}; size_t min_size{0}; size_t max_size{30}; std::basic_string<CharType> alphabet{"abcd...6789"}; }; } 

Limiters on multiple generated values


The library supports limiters on generated values, for example:


 std::cout << "The answer to the question of everything is:" << random<int>(between(42,42)) << std::endl; 

There are 2 types of limiters:


  1. Limiters on the generation algorithm. With their help, you can change the values ​​of parameters in the class value_generation_algorithm<T> .
  2. Limiters (rather, correctors) on the already generated value.

With this, they can be used in 2 ways :


  1. Passing them as a parameter to the random function, as in the example above. In this case, they will be applied only to the current algorithm / value.
  2. Creating scoped_limit based on scoped_limit and applying it to a set of types. Then the limiter is applied to all of the specified types for the entire depth of the generated type tree throughout the life of the scoped_limit.

To create custom constraints, you must declare a delimiter structure / class and implement one or both of the functions:


 struct dummy_algorithm_limit{}; struct dummy_value_limit{}; namespace datagen{ namespace limits { void adjust_algorithm(random_source_base&, dummy_algorithm_limit const& l, value_generation_algorithm<dummy>& a){ //      dummy } void adjust_value(random_source_base&, dummy_value_limit const& l, dummy& a){ //     dummy } } } 

Restrictions apply in the following order:


  1. scoped_limit for algorithms
  2. parametric algorithm limiters
    here is the generation of the object tree
  3. scoped_limit for values
  4. parametric value limiters

general information


The source of entropy in the library is the class random_source_impl, using <random> . But it is possible to override this by providing a specialization of the structure random_source_instance<int> at the compilation stage.
For today, the generation of the following containers from stl has been implemented (actually what I need in my work):



pairs of types from boost:



Limiters for them:



Tested on the msvc-14.0 compiler, requires c ++ 14. Unfortunately, gcc behaves a little differently, as a result of which the library code did not gather under mingw (gcc-6.3.0), but I think those who have constant contact with him can quickly fix it.
The library is in the public domain . Ideas and implementations of new types are welcome.


')

Source: https://habr.com/ru/post/326576/


All Articles