检测c++结构从单元测试更新
Detecting c++ structure is updated from unit test
我有一组数据结构,应该使用boost::序列化从一层传递到另一层。例如
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)和配置(发布,调试)中遭受结构大小的差异。
有什么好主意吗?
问题是,如果我在结构中添加新字段(我肯定会这样做)并且忘记更新单元测试,该字段将不会被单元测试覆盖。
我的问题是:如何检测结构(或类)被改变。
我的想法是使用static_assert(sizeof(DataType1) == HARD_CODED_VALUE)[…]
这不是一个可移植的解决方案(正如你自己所指出的)。
有什么好主意吗?
是的:你能从更新测试开始吗?
也就是说,不要决定结构中应该包含什么,然后添加它,然后更新测试(如果您没有忘记)。
相反,更新测试以检查新的序列化数据,然后确保更新的测试失败,然后更新代码以使测试通过。
这种方法(首先编写/更新单元测试)已经(部分地)被创建来解决这个问题。
测试优先的方法还有其他优点:
-
它巧妙地避免了YAGNI
-
它最小化过早优化
-
它自然演变为跟踪应用程序/实现的功能完整性。
向类定义添加注释,以提醒您在添加成员时必须调整序列化器。计算机能为你做的事情是有限的——这就是为什么代码审查很重要。让任何补丁由另一个程序员审查,有一组严格的测试用例,并希望最好的。
我相信你可以写一个clang插件,它可以确保一个特定的方法引用一个结构体的每个成员,但是你真的需要这个吗?你能把你的时间花在这个上面吗?也就是说,如果你尝试将尽可能多的工作卸载到计算机上,你会得到额外的积分。即使是static_assert
的把戏也是个好把戏。如果您使用一组针对您经常构建的特定ABI和体系结构的#ifdef
来保护它,它可能会做得很好。
怎么样?
// 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
}
};
这样您只需要在一个地方添加字段。
我有一个类似的问题,我在使用boost::fusion中找到了我的解决方案。在这里,您可以遍历结构体的所有成员。所以不需要再手工操作了。同时,您还可以获得编译时自省的良好特性。因此很容易打印完整结构体的内容。通过一小段模板代码转换成日志文件。
在单元测试中,您可以使用预期的结构大小进行static_assert检查:
static_assert( sizeof(DataType1)==16, "Structure changed. Update serialize method" );
您必须为每个平台(或仅为一个平台)设置结构的大小(检查中的数字)。
你可以在你的结构中添加静态变量"version",并在结构改变时增加它。
static int version = 1234;
然后在测试中写入
static_assert( DataType1::version == HARD_CODE_VALUE );
一个疯狂的方法是不直接使用成员。
创建聚合变量模板。创建数据成员模板
数据成员模板接受一个标记结构体。
覆盖data_member<tag,T>::operator^( tag )
,返回对T
的引用。也许对免费的operator^( data_member< tag, T >*, tag )
做同样的事情
现在你可以通过this^tag()
获得成员,这看起来像成员访问。如果你创建一个tag
的全局实例,你甚至可以删除()
。
你也有编译时对你的数据成员的反射,所以你可以写for_each_member
,并编写所有的序列化代码一次,并使用它为每个struct
。
访问控制和data_member
的其他类别可以在aggregate
模板中完成。
基于标签的数据构造可以用一个复杂而花哨的aggregate
构造函数来完成。
或者你可以等待真正的反射在c++中出现,可能在十年内。
或者,您可以将struct
转换为tuple
包装器,并使用类似上面的覆盖技巧来使this^tag
为基于名称的访问工作。
如果我们有一个包含int x, y
和double d
的struct foo
,我们想这样做,我们可以这样做:
// 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
中的两个成员变量共享相同的"命名空间",如果它们具有相同的名称则会发生冲突。
- 有什么好的方法可以让系统调用代理允许在单元测试中进行模拟
- 在子目录中使用target_sources()命令时用于单元测试(qtest)的项目结构
- VC++本机单元测试,找不到调试符号
- 用于交叉编译和CMake的预处理器宏的单元测试
- C++ 用于单元测试的模板模板
- 提升 1.64 单元测试编译失败
- 单元测试欧拉到四元数实现失败
- 运行 C++ 单元测试时LNK2005链接错误
- 禁用自动捕获 Googletest 单元测试中的C++异常
- 有没有办法在不使用 #ifdef 的情况下不编译发布版本中的单元测试函数体?
- 使用 Google Test 对自定义断言函数进行单元测试
- 如何将我的 CMake 项目配置为运行所有单元测试?
- 在Qt C++单元测试中动态加载QQuickWindow而不是QQuickWidget
- MS 本机单元测试 - 断言::线程失败不起作用
- 如何获取 CMake 单元测试的相对路径?
- QTimer 超时不会在单元测试中触发
- 如何在提升构建中设置环境变量以进行提升单元测试框架?
- 在 AtMega32a 上运行的C++的单元测试
- 使用 gtest 框架在单元测试代码中检查目标对象的私有变量的最佳实践是什么?
- 检测c++结构从单元测试更新