Jason Thomas

I like to make stuff

April 07, 2017 @ 16:50

Testing private member functions in C++

I'm a fan of tests, OK. Testing my code regularly means I can move onto the next thing I need to do without anxiety of breaking something. It's great - I can run tests before, and after, fixing a bug or making a feature and be sure I haven't created bugs.

This was all great until I started doing C++, which allows for encapsulation. Suddenly, having private data members and member functions became hard to test.

I'm a Python developer mainly (that's the language I use most days). In Python if a class has private functions then I can test them anyway. If you weren't already aware, Python doesn't support encapsulation but the convention is to use an _ for anything you want to be private.

You might write a class like this:

class Combiner(object):

    def __init__(self):
        self.number_attr = 1
        self.string_attr = '2'

    def _to_int(self, string):
        return int(string)

    def add_numbers(self):
        result = self.number_attr + self._to_int(self.string_attr)
        return result

This example is silly, but it doesn't matter. For my class, all I want is to ensure the class has the responsibility of converting the string to an int. So for the encapsulation to work, the person reading my code needs to understand what the _ is all about.

So for tests, I can ignore the _ in my Python class and edit private attributes.

It's not so simple in C++. Consider this example:

class Combiner
{
    private:
        int to_int(std::string string_attr);
    public:
        Combiner();
        int number_attr;
        std::string string_attr;
        int add_numbers();
};

Combiner::Combiner()
{
    number_attr = 1;
    string_attr = "2";
}

int Combiner::to_int(std::string string_attr)
{
    return stoi(string_attr);   
}

int Combiner::add_numbers()
{
    int result = number_attr + to_int(string_attr);
    return result;
}

We can’t test the private to_int member function here.

In C++ there are some hacks: in your test file, you can do #define private public, and to me this is a massive hack. I feel dirty even typing it... OK my shower's finished, now where was I? Oh right, and another hack is to make your unit test class a friend of the class to be tested, since the friend relationship allows accessing private members. This is better than the #define hack.

I think both of these options are undesirable. I also think a popular suggestion on Stack Overflow is really unhelpful: don't test private member functions. How is that a solution? Just don't test important internal functions? Of course you should test them.

There's one answer on Stack Overflow that gets it right, but I found some trouble understanding what the person was suggesting. At first I thought the OP was suggesting to have important functions outside the instance. Obviously that's not great.

What the person was actually suggesting, I think, is to define a class with public member functions, so you can test them, and then use composition to place that instance as a private member. To access the member functions, you'll still need to do that inside of the base class.

I say composition, not inheritance, because the relationship that we want is for our class to be "has" a set of functions, not "is" that set of functions. That's another rant, though.

Here's a working example, where the only member functions that a class is defined as having are publicly-available.

class Converter
{
    public:
        Converter();
        int to_int(std::string string_attr);
};

Converter::Converter(){}

int Converter::to_int(std::string string_attr)
{
    return stoi(string_attr);   
}

class Combiner
{
    private:
        Converter converter;
    public:
        Combiner();
        int number_attr;
        std::string string_attr;
        int add_numbers();
};

Combiner::Combiner()
{
    number_attr = 1;
    string_attr = "2";
}

int Combiner::add_numbers()
{
    int result = number_attr + converter.to_int(string_attr);
    return result;
}

A friend of mine said this has issues too; if the class with functions has static variables, in some cases could break encapsulation. He's right: that would break encapsulation, and this is no one-size-fits-all solution.

The result of doing this has a few benefits:

A downside is that I can't stop anyone directly initialising instances of the class with member functions. I also can't stop developers from smashing cinder bricks against their heads, but I'm comfortable with treating people like adults.

My rant is over.

TL;DR:

If you're searching for a hack to test private member functions, then that suggests there's a better way to structure your project.

log in