TDD:确定地测试成员初始化,给定C++中未定义的行为
TDD: Test member initialization deterministically, given undefined behavior in C++
注意:我知道active_
在我的例子中可以是"任何东西"。这不是这个问题的意义所在。这是关于让一个"未定义的值"可靠地通过单元测试。
编辑:从"无构造函数"更改为"空构造函数"。
我正在开发一个C++类,并且正在使用TDD。现在,我想确保bool
类成员被正确初始化——在构造函数中为其赋值。因此,我编写了以下测试(使用谷歌模拟/谷歌测试框架):
TEST(MyClass, isNotActiveUponCreation) {
MyClass my;
ASSERT_FALSE(my.isActive());
}
以及以下类别定义:
class MyClass {
public:
// Note: Constructor doesn't initialize active_
MyClass() {}
bool isActive() const { return active_; }
private:
bool active_;
};
问题:在我的机器上,即使active_
从未初始化,该测试当前总是通过。现在我们知道active_
的值是未定义的,因为它是一个基元类型,从未初始化过。所以理论上,它可能在某个时刻是true
,但最终,不可能知道。最重要的是,我无法使用这种方法可靠地测试是否缺少初始化。
有人知道我如何以确定性和可重复性的方式测试这种情况吗?或者我必须接受它,省略这种测试,并希望我永远不会忘记初始化一个布尔成员,或者其他测试总是会发现由此产生的缺陷?
在阅读TobiMcNamobi的答案后,我想起了placement new,并想到了如何解决我的问题。除非我在构造函数中初始化active_
,否则以下测试确实会失败:
#include <gmock/gmock.h>
#include <vector>
class MyClass {
public:
// Note: Constructor doesn't initialize active_
MyClass() {}
bool isActive() const { return active_; }
private:
bool active_;
};
TEST(MyClass, isNotActiveUponCreation) {
// Memory with well-known content
std::vector<char> preFilledMemory(sizeof(MyClass), 1);
// Create a MyClass object in that memory area using placement new
auto* myObject = new(preFilledMemory.data()) MyClass();
ASSERT_FALSE(myObject->isActive());
myObject->~MyClass();
}
现在我承认,这个测试不是最可读的,第一眼可能不会立即清楚,但它工作可靠,独立于任何第三方工具,如valgrind。付出额外的努力值得吗?我不确定。它在很大程度上依赖于MyClass
的内部结构,这将使它非常脆。无论如何,这是在C++中测试正确初始化对象的一种方法。。
一旦有了单元测试,这种问题实际上很容易进行单元测试。
只需在内存检查器下运行单元测试(linux上的valgrind,不确定windows上使用了什么)。
我没有创建gtest可执行文件,而是创建了一个简单的示例:
#include <iostream>
class MyClass {
public:
// Note: no constructor
bool isActive() const { return active_; }
private:
bool active_;
};
int main()
{
MyClass c; // line 17
std::cout << c.isActive() << std::endl;
}
在valgrind下运行它,我得到了下一个输出(修剪了不需要的行):
==9217==
==9217== Conditional jump or move depends on uninitialised value(s)
.....
==9217== by 0x40094F: main (garbage.cpp:17)
当你用valgrind执行单元测试时,你会遇到各种与内存访问有关的问题。您还将获得回溯。
我的小测试:
#include <stdlib.h>
#include <vector>
#include <iostream>
class MyClass {
public:
// Note: Constructor doesn't initialize active_
MyClass() {}
bool isActive() const { return active_; }
private:
bool active_;
};
int main(int argc, char* argv[])
{
std::vector<MyClass> vec(1000);
for (int i = 0; i < 1000; i++)
{
std::cout << (vec[i].isActive() ? "1" : "0");
}
return system("pause");
}
那么,当执行此操作(使用VS2012编译)时会发生什么呢?
调试配置:编写了一千个1。
发布配置:写了一千个0。我把迭代次数提高到100000,得到了十万个0。。。等等,前几个数字是10101110000000……然后就到了!像这样的另一个序列之间的某个地方是隐藏的。
这是什么意思?结果或多或少是意料之中的。您无法预测单个未初始化的位是如何设置的。这里要做的是初始化内存中的一些空间,并在那里创建一个对象,就好像该内存以前没有初始化一样。
因此,在我被证明是错误的之前:您不能对其进行单元测试。
除非你使用工具(例如valgrind,请参阅其他答案)。
我认为TDD很棒,但它当然也有局限性。我只需要初始化标志,然后继续红-绿重构循环。
TDD测试应该练习行为而不是实现细节。构造函数初始化、setter初始化等的测试取决于特定的实现,如果重构该实现,这些测试将很脆弱。
您要做的是对未定义的行为进行单元测试,这是毫无意义的,因为显然需要接受所有结果。
如果使用MemoryManitizer编译单元测试套件,那么从未初始化内存中读取的任何内容都会导致测试失败。
- 反向给定链表中的K节点
- std::condition_variable::wait()如何评估给定的谓词
- 给定n个元素的m个集合.在C++中找到出现在最大集合数中的元素
- 在C++中,是否可以基于给定的标识符创建基类的新实例,反之亦然
- 芬威克树(BIT).找到具有给定累积频率的最小索引,单位为 O(logN)
- 递归计数给定目录的文件和所有目录
- 从给定的 I 和 D 序列中形成最小数
- cpp二进制搜索问题,计算给定数组中输入元素的出现次数
- C++在变量给定的指定时间内关闭电脑
- 如何从给定字符串中删除第二次和第三次出现的$
- UE4-如何在给定4个屏幕坐标的情况下缩放纹理或材质
- 给定一个向量,如何找到该向量的所有子集和的原始索引
- 计算缩放多边形的比例,得到给定的多边形面积
- 我应该如何修改此代码以使用给定字符串中的字母打印菱形图案
- 如何在Directwrite中获得给定字体的可用OpenType功能
- 给定两个偶数,求出它们之间所有偶数的平方和
- 当给定默认值时,为什么此模板参数推导失败
- Boost::posix_time::ptime舍入到给定的分钟数
- 使用RAII在给定次数的迭代后重新分配资源
- 给定顺序中的事件处理