9

I'm taking my first steps with unit testing and have a problem with encapsulation. My class has some private member variables that shouldn't be visible to the client, but in order for me to put object in a state I want to test it under, I need to set those private variables.

Say I have a code like that:

Class Foo {

public:
  int action() ;  
private:
  int state ;

} ;

int Foo::action()
{
  if(this->state == 1)
    return 1 ;
  else
    return 0 ;
}

So now I want to test Foo::action(), but I need to be able to set Foo::state to be able to check function under different scenarios. One solution is the evil "define private public" in tests code. But is there something more elegant? I would like to stress that Foo::state is a variable that shouldn't be accessed by client, so I don't want to declare any public setter.

Edit:

I now think that extending the class I want to test in test code and including setters in that derived class would work, providing I changed private variables to protected. But that's a 'one generation only' solution and still feels like a hack rather than a proper approach.

Edit 2:

After reading answers and comments I was given (thanks to Lieven and ap. in particular) I believe the actual class I'm trying to test now (not the simple example I provided) simply does too much and the answer to my problem is moving some of its logic into another class that will be used by the big guy.

Puchatek
  • 1,477
  • 3
  • 15
  • 33
  • Possible duplicate of [How do I test a class that has private methods, fields or inner classes?](https://stackoverflow.com/questions/34571/how-do-i-test-a-class-that-has-private-methods-fields-or-inner-classes) – Raedwald Dec 14 '17 at 13:36

3 Answers3

4

There are only two posibilities (refactoring asside)

  1. Use the public interface to set the state.
  2. The state is redundant if you can't set it to through the public interface.

Option 2 is self explanatory and most likely not applicable to your case so you are left with setting the state through the public interface of your class.

As you have already mentioned, this is possible but it requires a lot of code to get to the right state. That in itself could be an indication that your class is currently doing to much and it's time to refactor parts of your class into smaller, testable classes.

From Do not test private methods

If you find the need to test a private method, then you’re doing something else wrong. There is an “upstream” problem, so to speak. You have arrived at this problem due to some other mistake previous in this process. Try to isolate what that is, exactly, and remove that rather than bending your tests to go down a painful road of brittleness in testing.

and Unit testing private members

I'd recommend not unit testing private methods. Since they're private, they may be changed in any conceivable (or inconceivable?) manner between each release. If a given private method is so critical to the operation of the class that you feel it deserves test cases, then it's probably time to refactor that out into a protected or public method

A common quote on this is

You should never touch your privates

Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146
  • I'm trying to set private member variables, not test private methods. Also while the state can be achieved through public input, it can't be done so immediately. I'm now writing an algorithm that walks a graph and what state will it be in at a given node depends on the path it used to arrive there and current node values. So basically the input is the graph and the starting node, but that's it, while I want to test that some function works fine 500 nodes later at some possible state. – Puchatek Dec 20 '12 at 12:44
  • 1
    @Puchatek - If you need to jump through hoops to get the object into the state you require, it might be an indication that you need to refactor you current object into smaller, testable objects. Your current object would get these smaller object injected to do it's work. You might want to read up onto dependency injection. I highly recommend reading Misco Hevery's blog(s). – Lieven Keersmaekers Dec 20 '12 at 14:03
2

If your test class is called MyTestClass, then add MyTestClass as friend in class Foo to be able to access its private member variables.

Class Foo {
public:
    int action();  
private:
    int state;

    friend class MyTestClass;
};
xavatage
  • 21
  • 1
  • I feel that there shouldn't be any traces of testing code in the actual code. Maybe just an opinion, but it doesn't feel right to me. – Puchatek Dec 20 '12 at 12:51
  • Sometimes you are in conflict with good practive and getting the job done and this is the way to go. – jopa Dec 20 '12 at 12:52
1

You should be having some publicly (or "protectedly") accessible mechanism to change the value of the private variable state. For simplicity, let's say it is a method Foo::setState(int inState). Use that in the unit test to change the state, and thus test the Foo::action() method. This ensures that any future implementation changes would not affect the unit test (unless the "API" Foo::setState() changes - in which case, of course, you have to change the unit test).

If you do not have such a mechanism to change state, it means the end user or calling code cannot change it either, and hence, you wouldn't need to test it (and maybe that makes state redundant, but I don't know about that).

If the private variable changes "indirectly" through other code, you will have to execute the same code in the unit test. You can always trace back any method visible externally to inputs fed to the code externally. Basically the point is that in the unit test, you would have to feed the same inputs to the code as in the "real" scenario, and then test if it responds as it should.

As discussed in the comments below, if the "path" from the input to the code being tested is too long, the code/test may have to be broken into smaller modules or intermediate points. To elaborate, consider the code flow as:

Input -> A -> B -> C -> Output
// A, B, C are intermediate execution points which are not "publicly accessible".

In the above case, all you can do is

Given Input, check if Output is correct.

Instead, it would be good to expose the intermediate A, B, C, at least to the unit tests, so that you can now break your test into:

Given Input, check if A is correct.

Given A, check if B is correct.

Given B, check if C is correct.

Given C, check if Output is correct.

As you can imagine, if the test fails, it becomes easier to figure out what failed, and hence to fix it.

Community
  • 1
  • 1
Masked Man
  • 1
  • 7
  • 40
  • 80
  • Well, if the "path" from the input to the code being unit tested is _too_ long, it usually means that the code/test needs to be refactored into smaller modules. Alternately, it could be that the test needs to be applied at a "higher level". From your description, it sounds like you are testing at a lower level of abstraction than what is required. – Masked Man Dec 20 '12 at 12:58
  • It is always a good idea to be able to apply tests at the "intermediate" points. If your code is written such that between the inputs and the state change, there is nowhere you can apply any tests, then you may want to consider refactoring that. – Masked Man Dec 20 '12 at 13:01
  • 1
    My personal opinion is that it is okay to expose the intermediate points, if only for testing, if it helps improve quality of the tests (and hence the code). Preprocessor can help you. Enclose the "test only" code inside a `#ifdef UNIT_TEST ... #endif`, and compile with `-DUNIT_TEST` when unit testing. It also ensures that the test only code is excluded from the "real" executable. – Masked Man Dec 20 '12 at 13:14
  • In program I'm working on right now I have the same algorithm being applied over and over to nodes of a graph. State of the algorithm (which decides its next action) depends on the current node and nodes around. So it's a bit like A -> A' -> A'' .... A'''''' rather than A -> B -> C. And if I want to test A''' I need to first get it to state A''. And here's the problem. @Lieven in another answer recommended reading about dependency injection, so I'll have a look there for now. – Puchatek Dec 21 '12 at 01:36