检测c ++结构从单元测试更新

时间:2013-05-29 10:10:39

标签: c++ unit-testing

我有一系列数据结构,应该使用boost :: serialization从一层传递到另一层。 例如

struct DataType1
{
    std::string field1;
    std::string field2;

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

我想在此编写单元测试,以确保我没有遗漏某些字段(有很多结构和字段)。

问题是,如果我在结构中添加新字段(我肯定会这样做)而忘记更新单元测试,则单元测试不会覆盖此字段。

我的问题是:如何检测该结构(或类)是否已更改。 我的想法是使用static_assert(sizeof(DataType1)== HARD_CODED_VALUE)但它在不同编译器,平台(x64,x86)和配置(发布,调试)中的结构大小不同。

任何好主意如何处理这个?

7 个答案:

答案 0 :(得分:3)

  

问题是,如果我在结构中添加新字段(我肯定会这样做)而忘记更新单元测试,则单元测试不会覆盖此字段。

     

我的问题是:如何检测结构(或类)是否已更改。

     

我的想法是使用static_assert(sizeof(DataType1)== HARD_CODED_VALUE)[...]

这不是便携式解决方案(正如您自己所说)。

  

任何好主意如何处理这个?

是的:您可以从更新测试开始吗?

也就是说,不要决定结构中的内容,然后添加它,然后更新测试(如果你不忘记)。

相反,更新测试以检查新的序列化数据,然后确保更新的测试失败,然后更新代码以便测试通过。

这种方法(首先是写入/更新单元测试)已经(部分)创建,以解决这个问题。

测试优先方法还有其他优点:

  • 它巧妙地避免了YAGNI

  • 它最大限度地减少了过早优化

  • 它会自然地发展,以跟踪您的应用程序/实现的功能完整性。

答案 1 :(得分:2)

在类定义中添加注释,以提醒您在添加成员时必须调整序列化程序。计算机可以为您做什么是有限的 - 这就是代码审查很重要的原因。让任何补丁都由其他程序员审核,拥有一套严格的测试用例并希望能够做到最好。

我相信你可以,例如编写一个clang插件,它将确保一个特定的方法引用一个结构的每个成员,但是你真的需要这个并且你能投入时间吗?

也就是说,你可以尝试将尽可能多的工作卸载到计算机上。即使是static_assert技巧也是一个很好的技巧。如果你使用一组#ifdef来保护它,对于一个特定的ABI和你经常建造的架构,它可能会做得很好。

答案 2 :(得分:1)

如下:

// 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
    }
};

这样你只需要在一个地方添加字段。

答案 3 :(得分:0)

在单元测试中,您可以使用预期的结构大小进行static_assert检查:

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

您必须为每个平台(或仅针对一个平台)设置结构的大小(检查中的数字)。

答案 4 :(得分:0)

您可以将静态变量“version”添加到结构中,并在结构更改时增加它。

static int version = 1234;

然后在你的测试中写下

static_assert( DataType1::version == HARD_CODE_VALUE );

但是你仍然可以忘记在更改结构时更新版本,或者在更新测试时忘记添加一些新成员。

答案 5 :(得分:0)

我有类似的问题,我在使用boost :: fusion时发现了我的解决方案。 在这里,您可以遍历结构的所有成员。所以不需要再手动了。 此外,您还可以获得编译时内省的好处。因此很容易打印完整结构的内容,例如。通过liitle模板代码进入日志文件。

答案 6 :(得分:0)

craay方法是不直接使用成员。

创建聚合变量模板。创建数据成员模板。

数据成员模板采用标记结构。

覆盖data_member<tag,T>::operator^( tag )以返回对T的引用。 Mqybe对免费operator^( data_member< tag, T >*, tag )

做同样的事情

现在您可以通过this^tag()获取该成员,该成员看起来像成员访问权限。如果您创建tag的全局实例,则甚至可以删除()

您的数据成员也有编译时间反映,因此您可以编写for_each_member,并编写所有序列化代码一次,并将其用于每个struct

访问控制和其他类别的data_member可以在aggregate模板中完成。

基于标记的数据构建可以使用复杂且花哨的aggregate构造函数来完成。

或者你可以等待真正的反思出现在C ++中,可能在十年之内。

或者,您可以将struct转换为tuple包装器,并使用类似上述覆盖技巧的方式使this^tag用于基于名称的访问。

如果我们要struct foo包含int x, ydouble d,我们可以执行以下操作:

// 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;
}

一个(严重)问题是两个不同struct中的两个成员变量共享相同的“命名空间”,如果它们具有相同的名称则会发生冲突。