C ++ structure detection updated with unit test

I have a family of data structures that need to be passed from one level to another using boost :: serialization. for example

struct DataType1 { std::string field1; std::string field2; template<class Archive> void serialize(Archive & ar, const unsigned int version) { ar & field1; ar & field2; } }; 

I want to write unit test to make sure that I have not missed some fields (there are many structures and fields).

The problem is that if I add a new field to the structure (I will definitely do it) and forget to update the unit test, this field will not be covered by unit test.

My question is: how to determine which structure (or class) is changed. My idea was to use static_assert (sizeof (DataType1) == HARD_CODED_VALUE), but it suffers from a difference in the size of the structure in different compilers, platforms (x64, x86) and configurations (release, debugging).

Any good idea how to handle this?

+8
c ++ unit-testing
source share
7 answers

The problem is that if I add a new field to the structure (I will definitely do it) and forget to update the unit test, this field will not be covered by unit test.

My question is: how to determine if the structure (or class) is changed.

My idea was to use static_assert (sizeof (DataType1) == HARD_CODED_VALUE) [...]

This is not a portable solution (as you yourself noted).

Any good idea how to handle this?

Yes: can you start by updating the test?

That is, do not decide what should be in the structure, then add it, then update the tests (if you do not forget).

Instead, update the tests to check for new serialized data , then make sure that the updated tests fail , and only then update the code so that the tests pass.

This approach (first an initial write / update test) was created (partially) to solve this particular problem.

The first approach to testing has other advantages:

  • he carefully avoids yagni

  • it minimizes premature optimization

  • it naturally evolves to track the completeness of your application / implementation.

+3
source share

Add a comment to the class definition to remind you that when adding items you need to set up a serializer. There are limits to what a computer can do for you - which is important for code review. Let any corrections be considered by another programmer, have a strict set of test cases and hope for the best.

I am sure that you could, for example, write a clang plugin that will make sure that a specific method refers to each member of the structure, but you really need it, and can you spend your time on this?

However, you have bonus points for trying to offload as much work on your computer as possible. Even the static_assert trick is a good one. If you protect it with the #ifdef set for one particular ABI and architecture, where you create often enough, this can do an excellent job.

+2
source share

How about something like:

 // DataType1_members.h FIELD_DEF(std::string, field1); FIELD_DEF(std::string, field2); // DataType1.h struct DataType1 { #define FIELD_DEF(type, name) type name #include "DataType1_members.h" #undef FIELD_DEF template<class Archive> void serialize(Archive & ar, const unsigned int version) { #define FIELD_DEF(type, name) ar & name #include "DataType1_members.h" #undef FIELD_DEF } }; 

Thus, you only need to add fields in one place.

+1
source share

In your unit tests, you can perform a static_assert check with the expected structure size:

 static_assert( sizeof(DataType1)==16, "Structure changed. Update serialize method" ); 

You must set the size of the structure (number on the check) for each platform (or only for one platform).

0
source share

You can simply add the static version variable to your structures and increase it when the structure changes.

 static int version = 1234; 

Then in your tests just write

 static_assert( DataType1::version == HARD_CODE_VALUE ); 

But you can still forget to update the version when changing the structure or forget to add some of the new members when updating the test.

0
source share

I had a similar problem and found my solution using boost :: fusion. Here you can iterate over all members of your structure. Therefore, there is no need to do this manually. In addition, you get a great opportunity to introspect compile time. Thus, it is easy to print the contents of the full structure, for example. to the log file using the liitle template code.

0
source share

The craay method is to not use elements directly.

Create a cumulative variation pattern. Create a data item template.

The data item template accepts a tag structure.

Replace data_member<tag,T>::operator^( tag ) to return a reference to T Mqybe does the same for the free operator^( data_member< tag, T >*, tag )

Now you can get the member through this^tag() , which looks like member access. If you make a global instance of tag , you can even remove () .

You also have a compilation time reflection for your data members. That way you can write for_each_member and write all your serialization code once and use it for each struct .

Access control and other data_member categories can be performed in the aggregate template.

Tag-based data construction can be accomplished using the sophisticated aggregate constructor.

Alternatively, you can wait for a real reflection in C ++ to appear, perhaps for a decade.

Alternatively, you can turn your struct into a tuple wrapper and use something like the above redefinition trick to get this^tag for name-based access.

If we have a struct foo containing int x, y and double d , we want to do this, we can do the following:

 // boilerplate template<typename C, std::size_t idx> struct Tag {}; template<typename C, std::size_t tag_idx> auto operator^(C&& lhs, Tag<C, tag_idx> const&>) -> decltype( std::get<tag_idx>( std::forward<C>(lhs) ) { return std::get<tag_idx>( std::forward<C>(lhs); } struct foo:std::tuple< int, int, double > {}; Tag< foo, 0 > x; // another annoying part: need to manually number them Tag< foo, 1 > y; // we can avoid this via an aggregate trick, but Tag< foo, 2 > d; // even that isn't all that pretty int main() { foo bar; bar^x = 7; bar^y = 3; bar^d = 3.14; } 

One (serious) problem is that two member variables in two different struct have the same “namespace” and conflict if they have the same name.

0
source share

All Articles