c++ 如何测试类的私有成员和方法?

nkhmeac6  于 2022-11-27  发布在  其他
关注(0)|答案(8)|浏览(517)

我尝试在一个名为VariableImpl的C++类上执行unit testing(使用Boost单元测试框架)。

class Variable
{
public:
  void UpdateStatistics (void) {
    // compute mean based on m_val and update m_mean;
    OtherClass::SendData (m_mean);
    m_val.clear ();
  }
  virtual void RecordData (double) = 0;

protected:
  std::vector<double> m_val;

private:
  double m_mean;
};

class VariableImpl : public Variable
{
public:
  virtual void RecordData (double d) {
    // Put data in m_val
  }
};

如何检查平均值计算是否正确?注意1)m_mean是受保护的,2)UpdateStatistics调用另一个类的方法,然后清除向量。
我能看到的唯一方法是添加一个getter(例如,GetMean),但我一点也不喜欢这个解决方案,也不认为它是最优雅的。
我该怎么办呢?
如果我测试的是私有方法而不是私有变量,我该怎么办?

5vf7fwbs

5vf7fwbs1#

总的来说,我同意其他人在这里所说的--只有公共接口应该进行单元测试。
尽管如此,我刚刚遇到了一个案例,我必须首先调用一个受保护的方法,为一个特定的测试案例做准备。这在Linux/愚者中可以正常工作,但在Windows和Visual Studio中却失败了。
原因是将protected更改为public也会更改损坏的符号名称,从而导致链接器错误:这个库提供了一个 protected__declspec(dllexport) void Foo::bar()方法,但是在#define存在的情况下,我的测试程序需要一个 public__declspec(dllimport) void Foo::bar()方法,这给了我一个无法解析的符号错误。
因此,我切换到基于friend的解决方案,在类头中执行以下操作:

// This goes in Foo.h
namespace unit_test {   // Name this anything you like
  struct FooTester; // Forward declaration for befriending
}

// Class to be tested
class Foo
{
  ...
private:
  bool somePrivateMethod(int bar);
  // Unit test access
  friend struct ::unit_test::FooTester;
};

在我的实际测试案例中,我这样做:

#include <Foo.h>
#include <boost/test/unit_test.hpp>

namespace unit_test {
  // Static wrappers for private/protected methods
  struct FooTester
  {
    static bool somePrivateMethod(Foo& foo, int bar)
    {
      return foo.somePrivateMethod(bar);
    }
  };
}

BOOST_AUTO_TEST_SUITE(FooTest);
BOOST_AUTO_TEST_CASE(TestSomePrivateMethod)
{
  // Just a silly example
  Foo foo;
  BOOST_CHECK_EQUAL(unit_test::FooTester::somePrivateMethod(foo, 42), true);
}
BOOST_AUTO_TEST_SUITE_END();

这适用于Linux/愚者以及Windows和Visual Studio。

lo8azlld

lo8azlld2#

在C++中测试受保护数据的一个好方法是分配朋友代理类:

#define FRIEND_TEST(test_case_name, test_name)\
friend class test_case_name##_##test_name##_Test

class MyClass
{
    private:
        int MyMethod();
        FRIEND_TEST(MyClassTest, MyMethod);
};

class MyClassTest : public testing::Test
{
    public:
      // ...
        void Test1()
        {
            MyClass obj1;
            ASSERT_TRUE(obj1.MyMethod() == 0);
        }

        void Test2()
        {
            ASSERT_TRUE(obj2.MyMethod() == 0);
        }

        MyClass obj2;
};

TEST_F(MyClassTest, PrivateTests)
{
    Test1();
    Test2();
}

查看更多Google Test(gtest).

oo7oh9g9

oo7oh9g93#

对VariableImpl进行单元测试,以便确保其行为,同时确保Variable行为
测试内部并不是世界上最糟糕的事情,但目标是只要接口契约得到保证,它们可以是任何东西。如果这意味着创建一堆奇怪的模拟实现来测试Variable,那么这是合理的。
如果这看起来很多,考虑到实现继承并没有创建很好的关注点分离。如果单元测试很难,那么对我来说这是一个很明显的代码气味。

lp0sw83n

lp0sw83n4#

虽然在我看来,测试类的私有成员/方法的需要是一种代码气味,但我认为这在C++中在技术上是可行的。
例如,假设您有一个Dog类,它具有除公共构造函数之外的私有成员/方法:

#include <iostream>
#include <string>

using namespace std;

class Dog {
  public:
    Dog(string name) { this->name = name; };

  private:
    string name;
    string bark() { return name + ": Woof!"; };
    static string Species;
    static int Legs() { return 4; };
};

string Dog::Species = "Canis familiaris";

现在,由于某种原因,您希望测试私有的,您可以使用privablic来实现。
包括一个名为privablic.h的头沿着所需的实现,如下所示:

#include "privablic.h"
#include "dog.hpp"

然后根据任何示例成员的类型Map一些存根

struct Dog_name { typedef string (Dog::*type); };
template class private_member<Dog_name, &Dog::name>;

...和示例方法;

struct Dog_bark { typedef string (Dog::*type)(); };
template class private_method<Dog_bark, &Dog::bark>;

对所有静态示例成员执行相同的操作

struct Dog_Species { typedef string *type; };
template class private_member<Dog_Species, &Dog::Species>;

...和静态示例方法。

struct Dog_Legs { typedef int (*type)(); };
template class private_method<Dog_Legs, &Dog::Legs>;

现在您可以测试所有这些功能:

#include <assert.h>

int main()
{
    string name = "Fido";
    Dog fido = Dog(name);

    string fido_name = fido.*member<Dog_name>::value;
    assert (fido_name == name);

    string fido_bark = (&fido->*func<Dog_bark>::ptr)();
    string bark = "Fido: Woof!";
    assert( fido_bark == bark);

    string fido_species = *member<Dog_Species>::value;
    string species = "Canis familiaris";
    assert(fido_species == species);

    int fido_legs = (*func<Dog_Legs>::ptr)();
    int legs = 4;
    assert(fido_legs == legs);

    printf("all assertions passed\n");
};

输出量:

$ ./main
all assertions passed

您可以查看test_dog.cppdog.hpp的源代码。

  • 免责声明 *:多亏了其他聪明人的洞察力,我组装了前面提到的“库”,它能够访问给定C++类的私有成员和方法,而不改变其定义或行为。为了使它工作,(显然)需要知道并包括类的实现。
    :我修改了此答案的内容,以遵循评审员建议的指示。
wj8zmpe1

wj8zmpe15#

我通常建议测试类的公共接口,而不是私有/受保护的实现。在这种情况下,如果它不能通过公共方法从外部世界观察到,那么单元测试可能不需要测试它。
如果功能需要子类别,请对真实的的衍生类别进行公寓测试,或建立您自己的测试衍生类别(具有适当的实作)。

olmpazwi

olmpazwi6#

Google测试框架示例:

// foo.h
#include "gtest/gtest_prod.h"
class Foo {
  ...
 private:
  FRIEND_TEST(FooTest, BarReturnsZeroOnNull);
  int Bar(void* x);
};

// foo_test.cc
...
TEST(FooTest, BarReturnsZeroOnNull) {
  Foo foo;
  EXPECT_EQ(0, foo.Bar(NULL));
  // Uses Foo's private member Bar().
}

主要思想是使用friendC++关键字。您可以扩展此示例,如下所示:

// foo.h
#ifdef TEST_FOO
#include "gtest/gtest_prod.h"
#endif

class Foo {
  ...
 private:
  #ifdef TEST_FOO
  FRIEND_TEST(FooTest, BarReturnsZeroOnNull);
  #endif
  int Bar(void* x);
};

您可以用两种方式定义TEST_FOO预处理程序符号:
1.在CMakeLists.txt文件中

option(TEST "Run test ?" ON)
 if (TEST)
   add_definitions(-DTEST_FOO)
 endif()

1.作为编译器的参数

g++ -D TEST $your_args
oo7oh9g9

oo7oh9g97#

嗯,* 单元 * 测试应该测试 * 单元 ,理想情况下,每个类都是一个自包含的单元--这直接遵循单一责任原则。
因此,测试类的私有成员应该是没有必要的--类是一个黑盒子,可以在单元测试中原样覆盖。
另一方面,这并不总是正确的,有时候有很好的理由(例如,类的几个方法可能依赖于一个私有的实用函数,而这个函数应该被测试)。一个非常简单,非常粗糙但最终成功的解决方案是将以下内容放入单元测试文件中,
在 * 包括定义类的头之前:

#define private public

当然,这破坏了封装,是 * 邪恶的 *。但对于测试来说,它服务于目的。

vs91vp4v

vs91vp4v8#

对于受保护的方法/变量,从类继承Test类并进行测试。
对于一个私人的,引入一个朋友类。这不是最好的解决方案,但它可以为你做的工作。
或者这个黑客:

#define private public

相关问题