4

Imagine we have a class like this:

class Testee
{
public:
   void Func()
private:
   void auxFunc()
};

and we want to do white-box unit-testing on it. Which do you think is a better approach? To declare the tester class a friend of the testee class? Or use the preprocessor like this:

  class Testee
    {
    public:
       void Func()
#ifndef UNITTEST_SYMBOL
    private:
#elif
    public:
#endif
       void auxFunc()
    };

and later in the testing file

#define UNITTEST_SYMBOL
#include "Testee.h"
#undef UNITTEST_SYMBOL

So, again, which do you think is a better approach? Or maybe you could suggest another approach.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • 1
    alternatively make auxFunc a seperate class and include a private object of that type in Testee – jk. Oct 08 '10 at 12:15
  • @jk: and make all the functions in auxFunc class public? – Armen Tsirunyan Oct 08 '10 at 12:16
  • 1
    if you want to test them, yes. arguably you should not be testing private methods, and if you are because of complexity it may be that you need to split the class http://stackoverflow.com/questions/105007/do-you-test-private-method – jk. Oct 08 '10 at 12:25

5 Answers5

4

How about:

#ifndef UNITTEST_SYMBOL
#define semiprivate private 
#else
#define semiprivate public
#endif

and declare your class like:

  class Testee
    {
    public:
       void Func()
    semiprivate:
       void auxFunc()
    };

or even, if you're daring enough, do #define private public when testing.

Lie Ryan
  • 62,238
  • 13
  • 100
  • 144
  • So, I guess you are in favor of the preprocessor. Could you please provide some arguments for that? – Armen Tsirunyan Oct 08 '10 at 11:45
  • 2
    Basically, testing code should not clutter regular code. IOW, you should be able to write code as if you're not going to test it. Using friend class, or adding the #ifdef macro inside the class itself clutters a class definition with things that aren't directly related to the actual code. `#define private public` then becomes the most ideal solution; however apparently such macro construction is technically illegal, therefore `semiprivate`. – Lie Ryan Oct 08 '10 at 11:53
  • But where we add this macro? A open macro like this will modify the private access specifiers in the files that are not required to be modified. – Raams Feb 04 '19 at 12:02
  • @Raams If you create your own keyword I have over here, it's much less likely to unexpectedly clash with existing libraries. In any case, since this is only for testing code, so having a few extra code that are excessively permissive shouldn't really be an issue. – Lie Ryan Feb 04 '19 at 14:12
2

In the unit test file. You could try

#define private public
#include "Testee.h"

This is what I do, it means that there isn't anything related to unit testing within the header file. I find this very useful as I find it hard to follow when there are lots of #ifdef within my code.

I then have all my other header files before the #define

Mumbles
  • 1,654
  • 3
  • 14
  • 16
  • 3
    Some compilers include the access specifier when mangling member function names, which will cause link errors if you break the One Definition Rule like this. – Mike Seymour Oct 08 '10 at 12:26
  • 1
    It is technically illegal, it may break rule 17.4.3.1.1 P 2 of c++03. But on gcc, and MS compilers it works. I haven't found a compiler where this has caused any problems. – Mumbles Oct 08 '10 at 12:45
1

Using the friend method, the declaration would depend on the name of the test class, so if you ever change it's name the declaration has to be changed as well. Moreover I use Unittest++ so the actual test calss name is formed by a macro.

The method with the define is less hassle then. Also, I'd just put the define as a global compiler option instead of the way you show, for example

gcc -DUNIT_TESTING_ON

#ifdef UNIT_TESTING_ON
  public: //or protected maybe
#else
  private:
#endif

Anyone reading this would also see what the purpose is, this is more clear than having to look up the definition of the friend to see why exactly you made it a friend.

stijn
  • 34,664
  • 13
  • 111
  • 163
1

Below is the way that people follow for white box testing,

#define private friend cTestDriver; private
#define protected friend cTestDriver; protected
//included all your class header from which you like to access
//the private/protected method
#include Testee.h"
#undef private
#undef protected

The class cTestDriver will have wrappers/setters/getters to access all the private and protected members.

Also you have to be sensible about the order of headers files.

For ex:

File1.h

#include "Testee.h"
--
--
--

file TestDriver.h

#include File1.h
#define private friend cTestDriver; private
#define protected friend cTestDriver; protected
//included all your class header from which you like to access
//the private/protected method
#include Testee.h"
#undef private
#undef protected

In the above case the Testee.h will be opened while we open File1.h. So the macros wont be effective here.

Note : You may get warning about multiple declaration of friend.

Raams
  • 167
  • 1
  • 7
0

A possible alternative I've used, albeit a in simple scenario, without friends or the preprocessor:

class Testee
{
public:
   void Func(int);
protected:
   int auxFunc(int);
private:
   Data data;
};

In test file, using doctest, but other frameworks should work as well:

class Testee_test : public Testee
{
public:
   int auxFunc(int x) { return Testee::auxFunc(x); }
}

TEST_CASE("some test") {
   Testee_test t;

   REQUIRE(t.auxFunc(1) == 42);
}

The important concept is keeping the data private. And you only declare as protected those methods that you want to use in your white box. The inconvenience is having to forward methods to the superclass. And of course this is probably not suitable if inheritance is already present or needed in the classes to be tested.

Joe Abbate
  • 1,692
  • 1
  • 13
  • 19