单元测试时从何处加载存根数据

Where to load stub data from when unit testing

本文关键字:存根 数据 加载 何处 单元测试      更新时间:2023-10-16

为了进行单元测试,我需要模拟一个网络响应。响应通常是一个字节流,存储为const vector<uint8_t>。然而,对于单元测试,我想用CPP文件中硬编码的数据或从同一解决方案中的文件中读取的数据来生成向量。我的示例数据大约是6kb。使用谷歌测试时,关于数据放置位置的一般指导是什么?

也许(a)您需要一个大的数据序列来扮演某个角色测试用例只会读取它。这也可能是(类)全局数据const访问。

也许(b)您需要某个角色的大量数据序列,其中测试用例将读取、修改或销毁它。这需要每个测试病例肾素初始化,并且具有非const通路。

也许两者都有。在任何一种情况下,传统的谷歌测试实现都会使用测试夹具为了封装数据的获取,将在夹具的虚拟Setup()成员功能的实现,以及通过fixture的getter方法访问它。

以下程序说明了一种夹具,该夹具在每种情况下都提供从文件中获取的可变数据和全局常量数据。

#include <vector>
#include <fstream>
#include <stdexcept>
#include "gtest/gtest.h"
class foo_test : public ::testing::Test
{
protected:
virtual void SetUp() {
std::ifstream in("path/to/case_data");
if (!in) {
throw std::runtime_error("Could not open "path/to/case_data" for input");
}
_case_data.assign(
std::istream_iterator<char>(in),std::istream_iterator<char>());
if (_global_data.empty()) {
std::ifstream in("path/to/global_data");
if (!in) {
throw std::runtime_error(
"Could not open "path/to/global_data" for input");
}
_global_data.assign(
std::istream_iterator<char>(in),std::istream_iterator<char>());
}
}
// virtual void TearDown() {}   
std::vector<char> & case_data() {
return _case_data;
}
static std::vector<char> const & global_data() {
return _global_data;
}
private:
std::vector<char> _case_data;
static std::vector<char> _global_data;
};
std::vector<char> foo_test::_global_data;
TEST_F(foo_test, CaseDataWipe) {
EXPECT_GT(case_data().size(),0);
case_data().resize(0);
EXPECT_EQ(case_data().size(),0);
}
TEST_F(foo_test, CaseDataTrunc) {
EXPECT_GT(case_data().size(),0);
case_data().resize(1);
EXPECT_EQ(case_data().size(),1);
}
TEST_F(foo_test, HaveGlobalData) {
EXPECT_GT(global_data().size(),0);
}

int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

对于情况(a),您也可以考虑在全局设置中获取数据成员函数的子类::testing::Environment,但我看不到喜欢那样做的一般原因。

。。。还是硬编码

然后是将测试数据保存在文件中还是硬代码中的问题它在测试源中在这一点上感到高兴的读者从现在起只会感到无聊

作为一个普遍的问题,这是一个在当时情况下的判断问题,我不认为谷歌测试的使用在很大程度上颠覆了天平。我认为首要考虑因素是:是否希望能够在没有重建测试套件

假设重建测试套件以更改此项是一项不可忽略的成本,并且预计该项目的内容在未来会有所不同的相关测试代码。或者它可以独立于相关联的测试代码而变化,用于被测系统的不同配置。在这种情况下,最好可以由的运行时参数选择的文件或其他源中的项测试套件。在谷歌测试中,class ::testing::Environment的子类化是为测试套件资源的参数化获取而设计的设施。

如果实际上测试数据项的内容与关联的测试代码,然后将其硬编码到测试用例中是最不可能的这是一个谨慎的选择。(和测试文件,而不是其他类型的运行时配置程序,具有可以在中进行版本控制的宝贵属性与源代码相同的系统。)

如果测试数据项的内容与关联的测试代码,那么我倾向于硬编码而不是提取它来自数据文件。只是有偏见,而不是教条主义。也许你的测试该套件使用强大的库设施来初始化公共API测试数据,例如也连接到测试管理和缺陷管理中的XML文件,系统。好的

我认为,如果一个测试数据文件是主要的测试资源-测试套件无法生成的资源-然后是其内容最好是有能力的维护人员能够容易理解的文本数据和操纵。在这种情况下,我当然会认为例如,C/C++十六进制常量的列表是文本数据-它是源代码。如果测试文件包含二进制或令人生畏的面向机器的数据那么测试套件最好包含从易读开始的生产方法主要资源。测试套件有时不可避免地依赖外部来源的"原型"二进制文件,但它们几乎不可避免地需要测试工程师和bug修复人员在hex编辑器面前脸色惨白。

考虑到主要测试数据对维护人员来说应该清晰可见的原则,我们可以将主要测试数据是"某种代码"作为一种规范:是无逻辑的,但它将是程序员使用的文本内容习惯于测量和编辑。

想象一下,一个由4096个64位无符号整数组成的特定序列(Big Magic Table)是测试软件所必需的,并且非常严格绑定到相关的测试代码。它可以硬编码为一个巨大的矢量或数组测试套件的某个源文件中的初始值设定项列表。可能是由测试套件从CSV格式或CSV标点线。

对于从数据文件中提取并反对硬编码,可以敦促(根据Andrew McDonnell的回答)这有价值地实现了对BMT的修订源文件。同样,可能会敦促任何构建框架的源代码大量的文字初始化往往是不可维护的,因此需要维护责任

但这两个观点都可能被以下观察所反驳:BMT的声明可以在一个源文件中进行编码。它可以是测试数据初始化的测试套件的代码审查策略必须如此编码——也许是在遵循独特命名的文件中习俗可以肯定的是,这是一项狂热的政策,但并不比一种坚持所有测试数据初始化程序都必须从文件中提取的方法。如果维护人员必须调查包含BMT的任何文件中的BMT,文件扩展名是.cpp.dat还是不管怎样:所有的问题都是"代码"的可理解性。

对于硬编码和从数据文件中提取,可以敦促从数据文件中提取必须引入不相关的源测试用例中的潜在失败-所有不应该发生错误可能无法从文件中读取正确的数据。这给测试开发,在真正的测试失败和无法从文件中获取测试数据,也无法清楚地诊断所有可能的情况后者的原因。

在谷歌测试和类似功能框架的情况下,这一点可以是在某种程度上,通过向多态fixture基类做广告来进行反击如CCD_ 9和CCD_。这些有助于测试用例中封装测试资源获取的测试开发人员或测试套件初始化,使其全部结束,要么成功,要么对于诊断出的故障,在运行任何测试用例的组成测试之前。RAII可以在设置故障和实际故障之间保持一个无问题的划分。

然而,对于数据文件来说,存在不可减少的文件处理开销路由框架的RAII特性存在操作开销不做任何减少。在我与交易的大型测试系统打交道时数据文件,数据文件只是比只需要在生成时存在并更正的源文件。数据文件更有可能在运行时丢失或放错地方,或者包含格式错误的内容,或者以某种方式被拒绝许可,或者不知怎么出现在错误的修订版上。它们在测试系统中的用途并不像源文件那样简单或严格控制事情发生在测试数据文件上的不应该发生是测试依赖它们的系统,并且与它们的数量成比例。

由于源文件可以卫生地封装测试数据初始化对于修订跟踪,对它们进行硬编码可以等同于从文件,预处理器作为编译的副产品进行提取。既然如此,为什么要用其他有额外负债的机器来提取呢?可能有很好的答案,比如建议的带有测试管理的XML接口,缺陷管理系统,但"这是测试数据,所以不要硬编码"不是一个好的。

即使测试套件必须支持以下系统的各种配置调用测试数据项的各种实例化的测试,如果数据项与测试套件的构建配置是一致的,您可以很好地(卫生地)对其进行硬编码,并让条件编译选择正确的硬编码。

到目前为止,我还没有对修订跟踪hygeine的论点提出质疑用于测试数据初始化程序的基于文件的分离。我刚刚做了指出初始化程序硬编码的常规源文件可以实现这种隔离。我不想破坏这种争论,但是我想在测试数据初始化器的狂热结论之前停止它原则上应始终从专用文件中提取——无论是源文件还是数据文件。

没有必要详细说明反对这一结论的理由。那样谎言测试代码在本地比一般吃披萨的人更难理解程序员将编写和组织不断增长的测试套件文件令人难以置信的速度远远超过必要或健康的速度。规范地,测试套件的所有主要资源都是"某种代码"。A.程序员的技能包括将代码划分为文件的技能以确保适当的修订跟踪hygeine。这不是一个机械的过程,这是一个专业知识,代码审查涵盖了这一点。然而,代码审查可以而且应该确保测试数据初始化相对于修订跟踪,它们已经完成,设计和制作良好就像在所有其他常规方面一样。

一句话:如果你想为各种测试套件运行相同版本的测试套件从文件中读取这些模拟网络响应。如果另一方面与测试套件的构建配置保持不变或协变,为什么不hard编码?

(注意-这个答案对任何单元测试框架都是通用的)

我更喜欢将测试数据文件作为单独的对象保存在修订控制系统中。这提供了以下好处:

  • 您可以对单元测试进行编码,以接受任何或多个数据文件来测试各种情况
  • 您可以根据需要跟踪数据中的更改

如果您不希望单元测试执行读取数据文件,这在某些情况下可能是必要的条件,您可以选择编写一个程序或脚本,生成在fixture设置中初始化向量的C++代码。