How to do unit testing on private members (and met

2019-01-23 02:34发布

问题:

This question already has an answer here:

  • How do I test a private function or a class that has private methods, fields or inner classes? 46 answers

I am very new to unit testing and I am a little confused.

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

My question is 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?

TIA,

Jir

回答1:

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.



回答2:

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 can do the work for you.

Or this hack

#define private public


回答3:

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/VisualStudio. 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/VisualStudio.



回答4:

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 goolge test (gtest): http://code.google.com/p/googletest-translations/



回答5:

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.



回答6:

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.



回答7:

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.



回答8:

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 cpp keyword. You can extend this example as following:

// 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 in two ways:

1)within the CMakeLists.txt

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

2)as arguments to your compiler

g++ -D TEST $your_args