今天和同事讨论了课堂上要不要测试私有成员或者私有状态。他几乎说服了我为什么这是有道理的。这个问题的目的并不是重复 StackOverflow 已经存在的有关测试私有成员的性质和原因的问题,例如:让单元测试成为它正在测试的类的友元有什么问题吗? https://stackoverflow.com/questions/4171310/what-is-wrong-with-making-a-unit-test-a-friend-of-the-class-it-is-testing
在我看来,同事的建议是将友元声明引入到单元测试实现类中有点脆弱。在我看来这是不行的,因为我们引入了被测试代码对测试代码的一些依赖,而测试代码已经依赖于被测试代码=>循环依赖。即使是像重命名测试类这样无辜的事情也会导致破坏单元测试并强制测试代码中的代码更改。
我想请 C++ 专家对另一个提案进行判断,该提案依赖于我们可以专门化模板函数这一事实。想象一下这个班级:
// tested_class.h
struct tested_class
{
tested_class(int i) : i_(i) {}
//some function which do complex things with i
// and sometimes return a result
private:
int i_;
};
我不喜欢为 i_ 设置 getter 只是为了使其可测试。所以我的建议是在类中声明“test_backdoor”函数模板:
// tested_class.h
struct tested_class
{
explicit
tested_class(int i=0) : i_(i) {}
template<class Ctx>
static void test_backdoor(Ctx& ctx);
//some function which do complex things with i
// and sometimes return a result
private:
int i_;
};
通过添加这个函数,我们可以使类的私有成员可测试。请注意,不依赖于单元测试类,也不依赖于模板函数实现。在此示例中,单元测试实现使用 Boost Test 框架。
// tested_class_test.cpp
namespace
{
struct ctor_test_context
{
tested_class& tc_;
int expected_i;
};
}
// specialize the template member to do the rest of the test
template<>
void tested_class::test_backdoor<ctor_test_context>(ctor_test_context& ctx)
{
BOOST_REQUIRE_EQUAL(ctx.expected_i, tc_.i_);
}
BOOST_AUTO_TEST_CASE(tested_class_default_ctor)
{
tested_class tc;
ctor_test_context ctx = { tc, 0 };
tested_class::test_backdoor(ctx);
}
BOOST_AUTO_TEST_CASE(tested_class_value_init_ctor)
{
tested_class tc(-5);
ctor_test_context ctx = { tc, -5 };
tested_class::test_backdoor(ctx);
}
通过仅引入一个根本不可调用的模板声明,我们为测试实现者提供了将测试逻辑转发到函数中的可能性。该函数作用于类型安全上下文,并且由于测试上下文的匿名类型性质,仅在特定测试编译单元内部可见。最好的事情是,我们可以定义任意数量的匿名测试上下文,并对它们进行专门的测试,而无需触及被测试的类。
当然,用户必须知道什么是模板专业化,但是这段代码真的很糟糕、很奇怪或不可读吗?或者我是否可以期望 C++ 开发人员了解什么是 C++ 模板专业化及其工作原理?
详细说明使用朋友声明单元测试类我认为这并不可靠。想象一下 boost 框架(或者可能是其他测试框架)。它为每个测试用例生成一个单独的类型。但只要我能写,我为什么要关心:
BOOST_AUTO_TEST_CASE(tested_class_value_init_ctor)
{
...
}
如果使用朋友,我必须将每个测试用例声明为朋友...或者最终在某些常见类型(如固定装置)中引入一些测试功能,将其声明为朋友,并将所有测试调用转发到该类型。 . 这不是很奇怪吗?
我想看看您在实践这种方法时的优点和缺点。