访问私有变量的单元测试

Unit test accessing private variables

本文关键字:单元测试 变量 访问      更新时间:2023-10-16

我有一个单元测试类Tester;我想让它访问Working类的私有字段。

class Working {
    // ...
    private:
    int m_variable;
};
class Tester {
    void testVariable() {
        Working w;
        test( w.m_variable );
    }
}

我有以下选项:

  • make m_variable public - ugly
  • make method test_getVariable() - overcomplicated
  • friend class Tester添加到Working中-然后Working明确地"知道"测试器,这是不好的

我的理想是

class Working {
    // ...
    private:
    int m_variable;
    friend class TestBase;
};
class TestBase {};
class Tester : public TestBase {
    void testVariable() {
        Working w;
        test( w.m_variable );
    }
}

where Working知道TestBase,但不知道每个测试…但这行不通。很明显,继承了遗产,友谊就不成立了。

这里最优雅的解决方案是什么?

我同意Trott的回答,但有时您会将单元测试添加到不适合它的遗留代码中。在这些情况下,我会选择#define private public。它只适用于单元测试,并且只适用于重构成本太高而不愿费心的时候。这是丑陋的,技术上是非法的,但非常有效。

一般来说,单元测试不应该计算私有变量。把测试写在接口上,而不是实现上。

如果您确实需要检查私有变量是否具有特定的特征,请考虑使用assert(),而不是尝试为它编写单元测试。

一个更长的答案(为c#而不是c++编写,但适用相同的原则)是https://stackoverflow.com/a/1093481/436641。

-fno-access-control

如果您只使用GCC,您可以在编译单元测试时使用编译器选项-fno-access-control。这将导致GCC跳过所有访问检查,但仍然保持类布局相同。我不知道其他编译器是否有类似的选项,所以这不是一般的解决方案。

尽量使用公共接口测试所有私有代码。这不仅减少了最初的工作量,而且更改实现时,单元测试仍然工作的可能性要高得多。

也就是说,有时您只需要戳一下内部就可以获得良好的测试覆盖率。在这种情况下,我使用一种称为expose的习惯用法。如果你仔细想想,这里面有个笑话。

需要测试的Foo类

class Foo
{
public:
   // everyone is on their honor to only use Test for unit testing.
   // Technically someone could use this for other purposes, but if you have
   // coders purposely doing bad thing you have bigger problems.
   class Test;
   void baz( void );
private:
   int m_int;
   void bar( void );
};

foo_exposed.h只对单元测试代码可用。

class Foo::Test : public Foo
{
public:
   // NOTE baz isn't listed
   // also note that I don't need to duplicate the
   // types / signatures of the private data.  I just
   // need to use the name which is fairly minimal.
   // When i do this I don't list every private variable here.
   // I only add them as I need them in an actual unit test, YAGNI.
   using Foo::m_int;
   using Foo::bar;
};

// yes I'm purposely const smashing here.
// The truth is sometimes you need to get around const
// just like you need to get around private
inline Foo::Test& expose( const Foo& foo )
{
   return * reinterpret_cast<Foo::Test*>(
         &const_cast<Foo::Test&>( foo )
      );
}

如何在单元测试代码中使用

#include "foo_exposed.hpp"
void test_case()
{
   const Foo foo;
   // dangerous as hell, but this is a unit test, we do crazy things
   expose(foo).m_int = 20;
   expose(foo).baz();
}

如果您必须这样做,您可以有条件地编译您的代码,使TestBase仅在单元测试时是友元:

class Working {
    // ...
    private:
    int m_variable;
#ifdef UNIT_TESTING
    friend class TestBase;
#endif
};

我通过在我的测试中使用缺少"private"访问说明符的类头文件的副本来做到这一点。副本由测试目录中的makefile生成,以便在原始更改时重新生成副本:

 perl -ne 'print unless m/private:/;' < ../include/class_header.h > mock_class_header.h

和'test' make target依赖于mock_class_header.h。

这将授予对测试中所有私有成员变量的访问权,即使真正的库是用这些私有成员变量编译的。