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

发布于 2024-11-25 22:59:49 字数 922 浏览 0 评论 0原文

我正在尝试进行单元测试(使用Boost 单元测试框架)位于名为的 C++ 类上VariableImpl。以下是详细信息。

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 调用另一个类的方法,然后清除向量。

我能看到的唯一方法是添加一个吸气剂(例如,GetMean),但我根本不喜欢这个解决方案,我也不认为它是最优雅的。

我该怎么办?

如果我要测试私有方法而不是私有变量,我该怎么办?

I am trying to do unit testing (using the Boost unit testing framework) on a C++ class called VariableImpl. Here are the details.

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
  }
};

How can I check that the mean is computed correctly? Note that 1) m_mean is protected and 2) UpdateStatistics calls a method of another class and then clears the vector.

The only way I can see would be to add a getter (for instance, GetMean), but I don't like this solution at all, nor I think it is the most elegant.

How should I do?

And what should I do if I were to test a private method instead of a private variable?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

三岁铭 2024-12-02 22:59:49

嗯,单元测试应该测试单元,理想情况下每个类都是一个独立的单元 - 这直接遵循单一责任原则。

因此,测试类的私有成员是不必要的——该类是一个黑匣子,可以按原样在单元测试中覆盖。

另一方面,这并不总是正确的,有时有充分的理由(例如,该类的多个方法可能依赖于应该测试的私有实用程序函数)。一个非常简单、非常粗糙但最终成功的解决方案是将以下内容放入单元测试文件中,包括定义类的标头之前:

#define private public

当然,这会破坏封装并且是邪恶的。但对于测试来说,它达到了目的。

Well, unit testing should test units and ideally every class is a self-contained unit – this follows directly from the single responsibility principle.

So testing private members of a class shouldn’t be necessary – the class is a black box that can be covered in a unit test as-is.

On the other hand, this isn’t always true, and sometimes with good reasons (for instance, several methods of the class could rely on a private utility function that should be tested). One very simple, very crufty but ultimately successful solution is to put the following into your unit-test file, before including the header that defines your class:

#define private public

Of course, this destroys encapsulation and is evil. But for testing, it serves the purpose.

穿越时光隧道 2024-12-02 22:59:49

对于受保护的方法/变量,从该类继承一个 Test 类并进行测试。

对于私人,介绍一个朋友班。它不是最好的解决方案,但它可以为您完成工作。

或者这个黑客:

#define private public

For a protected method/variable, inherit a Test class from the class and do your testing.

For a private, introduce a friend class. It isn't the best of solutions, but it can do the work for you.

Or this hack:

#define private public
独夜无伴 2024-12-02 22:59:49

总的来说,我同意其他人在这里所说的——只有公共接口应该进行单元测试。

尽管如此,我刚刚遇到了一个情况,我必须首先调用受保护的方法,为特定的测试用例做准备。我首先尝试了上面提到的#define protected public方法;这适用于 Linux/GCC,但不适用于 Windows 和 Visual Studio。

原因是,将 protected 更改为 public 也会更改损坏的符号名称,从而给我带来链接器错误:库提供了 protected __declspec(dllexport) void Foo::bar() 方法,但使用了 #define 后,我的测试程序需要一个 public __declspec( dll导入)无效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/GCC 以及 Windows 和 Visual ;工作室。

In general, I agree with what others have said on here - only the public interface should be unit tested.

Nevertheless, I've just had a case where I had to call a protected method first, to prepare for a specific test case. I first tried the #define protected public approach mentioned above; this worked with Linux/GCC, but failed with Windows and Visual Studio.

The reason was that changing protected to public also changed the mangled symbol name and thus gave me linker errors: the library provided a protected __declspec(dllexport) void Foo::bar() method, but with the #define in place, my test program expected a public __declspec(dllimport) void Foo::bar() method which gave me an unresolved symbol error.

For this reason, I switched to a friend based solution, doing the following in my class header:

// 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;
};

And in my actual test case, I did this:

#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();

This works with Linux/GCC as well as Windows and Visual Studio.

·深蓝 2024-12-02 22:59:49

在 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< /a>(gtest)。

A good approach to test the protected data in C++ is the assignment of a friend proxy class:

#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();
}

See more Google Test (gtest).

划一舟意中人 2024-12-02 22:59:49

对 VariableImpl 进行单元测试,如果其行为得到保证,则 Variable 的行为也得到保证。

测试内部结构并不是世界上最糟糕的事情,但目标是只要确保接口契约,它们可以是任何东西。如果这意味着创建一堆奇怪的模拟实现来测试变量,那么这是合理的。

如果这看起来很多,请考虑实现继承并不会产生很大的关注点分离。如果很难进行单元测试,那么对我来说这是一个非常明显的代码味道。

Unit test VariableImpl such that if its behavior is ensured, so is Variable.

Testing internals isn't the worst thing in the world, but the goal is that they can be anything as long as the interfaces contracts are ensured. If that means creating a bunch of weird mock implementations to test Variable, then that is reasonable.

If that seems like a lot, consider that implementation inheritance doesn't create great separation of concerns. If it is hard to unit test, then that is a pretty obvious code smell for me.

枫以 2024-12-02 22:59:49

虽然在我看来,测试类的私有成员/方法的需要是一种代码味道,但我认为这在 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"

然后根据任何实例成员的类型

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++ 类的私有成员和方法,而无需更改其定义或行为。为了使其工作,(显然)需要了解并包含该类的实现。

注意:我修改了这个答案的内容,以遵循审稿人建议的指示。

While in my opinion the need of testing private members/methods of a class is a code smell, I think that is technically feasible in C++.

As an example, suppose you have a Dog class with private members/methods except for the public constructor:

#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";

Now for some reason you would like to test the private ones. You could use privablic to achieve that.

Include a header named privablic.h along with the desired implementation like that:

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

then map some stubs according to types of any instance member

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

...and instance method;

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

do the same with all static instance members

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

...and static instance methods.

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

Now you can test them all:

#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");
};

Output:

$ ./main
all assertions passed

You can look at the sources of test_dog.cpp and dog.hpp.

DISCLAIMER: Thanks to insights of other clever people, I have assembled the aforementioned "library" able to access to private members and methods of a given C++ class without altering its definition or behaviour. In order to make it work it's (obviously) required to know and include the implementation of the class.

NOTE: I revised the content of this answer in order to follow directives suggested by reviewers.

燕归巢 2024-12-02 22:59:49

我通常建议测试类的公共接口,而不是私有/受保护的实现。在这种情况下,如果无法通过公共方法从外界观察到它,那么单元测试可能就不需要测试它。

如果功能需要子类,则可以对真正的派生类进行单元测试,或者创建您自己的具有适当实现的测试派生类。

I generally suggest testing the public interface of your classes, not the private/protected implementations. In this case, if it can't be observed from the outside world by a public method, then the unit test may not need to test it.

If the functionality requires a child class, either unit test the real derived class OR create your own test derived class that has an appropriate implementation.

安人多梦 2024-12-02 22:59:49

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().
}

主要思想是使用 friend< /strong> C++ 关键字。
您可以按如下方式扩展此示例:

// 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 文件

     选项(TEST“运行测试?”ON)
     如果(测试)
       添加定义(-DTEST_FOO)
     结束()
    
  2. 作为编译器的参数

     g++ -D TEST $your_args
    

Example from the Google testing framework:

// 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().
}

The main idea is the use of the friend C++ keyword.
You can extend this example as follows:

// 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);
};

You can define the TEST_FOO preprocessor symbol in two ways:

  1. within the CMakeLists.txt file

     option(TEST "Run test ?" ON)
     if (TEST)
       add_definitions(-DTEST_FOO)
     endif()
    
  2. as arguments to your compiler

     g++ -D TEST $your_args
    
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文