Testing private class member in C++ without friend [duplicate]
Asked Answered
L

9

18

Today I had a discussion with a colleague on whether to test or not to test private members or private state in the class. He almost convinced me why it makes sense. This question does not aim to duplicate already existing StackOverflow questions about the nature and reason of testing private members, like: What is wrong with making a unit test a friend of the class it is testing?

Colleagues suggestion was in my opinion a bit fragile to introduce the friend declaration to the unit test implementation class. In my opinion this is a no-go, because we introduce some dependency of tested code to the test code, whereas test code already depends on tested code => cyclic dependency. Even such innocent things like renaming a test class results in breaking unit tests and enforces code changes in tested code.

I'd like to ask C++ gurus to judge on the other proposal, which relies on the fact that we are allowed to specialize a template function. Just imagine the class:

// tested_class.h

struct tested_class 
{
  tested_class(int i) : i_(i) {}

  //some function which do complex things with i
  // and sometimes return a result

private:
  int i_;
};

I don't like the idea to have a getter for i_ just to make it testable. So my proposal is 'test_backdoor' function template declaration in the class:

// tested_class.h

struct tested_class 
{
  explicit
  tested_class(int i=0) : i_(i) {}

  template<class Ctx>
  static void test_backdoor(Ctx& ctx);

  //some function which do complex things with i
  // and sometimes return a result

private:
  int i_;
};

By adding just this function we can make the class' private members testable. Note, there is no dependency to unit test classes, nor the template function implementation. In this example the unit test implementation uses Boost Test framework.

// tested_class_test.cpp

namespace
{
  struct ctor_test_context
  {
    tested_class& tc_;
    int expected_i;
  };
}

// specialize the template member to do the rest of the test
template<>
void tested_class::test_backdoor<ctor_test_context>(ctor_test_context& ctx)
{
  BOOST_REQUIRE_EQUAL(ctx.expected_i, tc_.i_);
}

BOOST_AUTO_TEST_CASE(tested_class_default_ctor)
{
  tested_class tc;
  ctor_test_context ctx = { tc, 0 };
  tested_class::test_backdoor(ctx);
}

BOOST_AUTO_TEST_CASE(tested_class_value_init_ctor)
{
  tested_class tc(-5);
  ctor_test_context ctx = { tc, -5 };
  tested_class::test_backdoor(ctx);
}

By introducing just a single template declaration, which is not callable at all, we give the test implementer a possibility to forward test logic into a function. The function, acts on type safe contexts and is only visible from inside the particular test compilation unit, due to anonymous type nature of test context. And the best thing is, we can define as many anonymous test contexts as we like and specialize tests on them, without ever touching the tested class.

Sure, the users must know what template specialization is, but is this code really bad or weird or unreadable? Or can I expect from C++ developers to have the knowledge what C++ template specialization is and how it works?

Elaborating on using friend to declare unit test class I don't think this is robust. Imagine boost framework (or may be other test frameworks). It generates for every test case a separate type. But why should I care as long I can write:

BOOST_AUTO_TEST_CASE(tested_class_value_init_ctor)
{
  ...
}

If using friends, I had to declare each test case as a friend then... Or end up introducing some test functionality in some common type (like fixture), declare it as a friend, and forward all test calls to that type... Isn't that weird?

I would like to see your pro and cons practicing this approach.

Lyndseylyndsie answered 12/7, 2012 at 18:26 Comment(2)
I'm still wrapping my head around your problem and proposed solution. The argument between using a friend and specialization seems like a semantic argument. In both cases you've opened a hole in your class to get around privacy. My first impression is that you are moving the complexity around rather than eliminating it (you still have the same amount of work involved in adding new tests). IMHO, specialization is the weirder of the two solutions. You could also use a friend function that forwards too (for the same price). Friends are easier to understand and maintain.Paresh
Friend functions risk other programmers using them. All things considered your posted solution is likely the way to go about it: your solution minimizes the work to implement it and hides what should be hidden when it should be hidden without affecting the memory layout of the class. Even better it is extremely unlikely that others writing code outside the test code will even touch your test_backdoor() hook due the namespace trick. But if they do, you can write a script to pre-process all code to detect illegal uses of such using grep.Sloop
S
3

What will follow is not technically speaking a straight answer to your question as it will still make use of the "friend" functionality but it does not require modification of the tested entity itself and I think it addesses the concern of breaking the encapsulation mentioned in some of the other answers; it does though require writing some boilerplate code.

The idea behind it is not mine and the implementation is entirely based on a trick presented and explained by litb on his blog(coupled with this Sutter's gotw for just a little bit more context, at least for me) - in short CRTP, friends, ADL and pointers to members (I must confess that to my dismay the ADL part I still don't get it entirely, but I'm relentesly working in figuring it out 100%).

I tested it with gcc 4.6, clang 3.1 and VS2010 compilers and it works perfectly.

/* test_tag.h */
#ifndef TEST_TAG_H_INCLUDED_
#define TEST_TAG_H_INCLUDED_

template <typename Tag, typename Tag::type M>
struct Rob
{
    friend typename Tag::type get(Tag)
    {
        return M;
    }
};

template <typename Tag, typename Member> 
struct TagBase
{
    typedef Member type;
    friend type get(Tag);
};


#endif /* TEST_TAG_H_INCLUDED_ */

/* tested_class.h */
#ifndef TESTED_CLASS_H_INCLUDED_
#define TESTED_CLASS_H_INCLUDED_

#include <string>

struct tested_class
{
    tested_class(int i, const char* descr) : i_(i), descr_(descr) { }

private:
    int i_;
    std::string descr_;
};

/* with or without the macros or even in a different file */
#   ifdef TESTING_ENABLED
#   include "test_tag.h"

    struct tested_class_i : TagBase<tested_class_i, int tested_class::*> { };
    struct tested_class_descr : TagBase<tested_class_descr, const std::string tested_class::*> { };

    template struct Rob<tested_class_i, &tested_class::i_>;
    template struct Rob<tested_class_descr, &tested_class::descr_>;

#   endif

#endif /* TESTED_CLASS_H_INCLUDED_ */

/* test_access.cpp */
#include "tested_class.h"

#include <cstdlib>
#include <iostream>
#include <sstream>

#define STRINGIZE0(text) #text
#define STRINGIZE(text) STRINGIZE0(text)

int assert_handler(const char* expr, const char* theFile, int theLine)
{
    std::stringstream message;
    message << "Assertion " << expr << " failed in " << theFile << " at line " << theLine;
    message << "." << std::endl;
    std::cerr << message.str();

    return 1;
}

#define ASSERT_HALT() exit(__LINE__)

#define ASSERT_EQUALS(lhs, rhs) ((void)(!((lhs) == (rhs)) && assert_handler(STRINGIZE((lhs == rhs)), __FILE__, __LINE__) && (ASSERT_HALT(), 1)))

int main()
{
    tested_class foo(35, "Some foo!");

    // the bind pointer to member by object reference could
    // be further wrapped in some "nice" macros
    std::cout << " Class guts: " << foo.*get(tested_class_i()) << " - " << foo.*get(tested_class_descr()) << std::endl;
    ASSERT_EQUALS(35, foo.*get(tested_class_i()));
    ASSERT_EQUALS("Some foo!", foo.*get(tested_class_descr()));

    ASSERT_EQUALS(80, foo.*get(tested_class_i()));

    return 0; 
}
Sharenshargel answered 24/7, 2012 at 0:5 Comment(0)
I
22

I think unit testing is about testing the observable behavior of the class under test. Therefore there is no need to test private parts as they themselves are not observable. The way you test it is by testing whether the object behaves the way you expect it (which implicitly implies that all private internal states are in order).

The reason for not to be concerned about the private parts is that this way you can change the implementation (e.g. refactoring), without having to rewrite your tests.

So my answer is don't do it (even if technically possible to) as it goes against the philosophy of unit tests.

Ianthe answered 17/7, 2012 at 1:28 Comment(4)
Part of the gain in unit testing is breaking the system up into small enough units that they have easily expressible behaviours. If you have lots of complicated private code you feel should be tested as multiple units, perhaps refactoring it into a concert of smaller classes rather than one big one is the answer.Octofoil
Pete: No, it is not an answer, since I explicitly wrote that I am not interested in that approach. I feel your and Attila's answers being an off-topic. You are both right if being in the ideal world and I know that too. This is not new to me. Did you ever work with a really complex legacy code which needs to be sanitized/refactored at all? And this is going to be a really long lasting part. Here you can't just break up everything in smaller pieces. This is non-ideal world where you sometimes really need a portable way to access privates for testing purposes.Lyndseylyndsie
@Lyndseylyndsie - I think I understand better now where you are coming from with the question. To address the immediate need to be able to test legacy code, the template function declaration will be the easiest & most portable way. This said, you should still strive to eventually remove this approach and replace it with pure unit testing (i.e. testing only public/protected parts). The reason being that if you couple the test to private parts, you will have to double your efforts every time you make a change to the tested code: the test needs to change as well -- even for a small reorganizationIanthe
-1: This is an idealistic response and almost always non-applicable in practice, given the vast amounts of legacy code we deal with as engineers.Gand
H
6

Pros

  • You can access the private members to test them
  • Its a fairly minimal amount of hack

Cons

  • Broken encapsulation
  • Broken encapsulation that is more complicated and just as brittle as friend
  • Mixing test with production code by putting test_backdoor on the production side
  • Maintance problem ( just like friending the the test code, you've created an extremely tight coupling with your test code )

All of the Pros/Cons aside, I think you are best off making some architectural changes that allow better testing of whatever complex stuff is happening.

Possible Solutions

  • Use the Pimpl idiom, put the complex code in the pimpl along with the private member, and write a test for the Pimpl. The Pimpl can be forward declared as a public member, allowing external instantiation in the unit test. The Pimpl can consist of only public members, making it easier to test
    • Disadvantage: Lots of code
    • Disadvantage: opaque type that can be more difficult to see inside of when debugging
  • Just test the public/protected interface of the class. Test the contract that your interface lays out.
    • Disadvantage: unit tests are difficult/impossible to write in an isolated manner.
  • Similar to the Pimpl solutions, but create a free function with the complex code in it. Put the declaration in a private header ( not part of the libraries public interface ), and test it.
  • Break encapsulation via friend a test method/fixture
    • Possible variation on this: declare friend struct test_context;, put your test code inside of methods in the implementation of struct test_context. This way you don't have to friend each test case, method, or fixture. This should reduce the likelyhood of someone breaking the friending.
  • Break encapsulation via template specialization
Hottempered answered 17/7, 2012 at 4:39 Comment(17)
Please edit with more pros/cons!Hottempered
Zac: Regarding Encapsulation: Yes this breaks encapsulation, but again I'd like to get back to Herb Sutter's explanation from gotw.ca/gotw/076.htm: "[...] This isn't actually a problem. The issue here is of protecting against Murphy vs. protecting against Machiavelli... that is, protecting against accidental misuse (which the language does very well) vs. protecting against deliberate abuse (which is effectively impossible). In the end, if a programmer wants badly enough to subvert the system, he'll find a way, as demonstrated ..." in the GotW #76. He also states to not doing that.Lyndseylyndsie
Zac: on "Mixing test with production code". Actually, I don't. This is just an unimplemented function declaration. Yes this is a part of the public static class interface, but it is impossible to call that function without providing an explicit implementation.Lyndseylyndsie
Zac: on Maintenance problem: This is not true. There is no dependency on the test code. =friend= assumes there exists a class or function with such a name (or signature). This template assumes nothing. Renaming the test case or test function does not automatically involves compilation errors. Aim of unit tests to quickly identify what is wrong in the class or a class member. If I change the implementation, I want the particular unit test fail ASAP and not 5 layers up. The second would already be a functional test.Lyndseylyndsie
Zac: on Pimpl: Pimple does not help here. Pimpl hides even more details and is even not accessible as a type, since pimpls in C++ are usually private inner type declarations, having their implementations in compilation units. To test a pimpl one needs to make inclusion of the .cpp file into the unit test implementation. Pimpl hides even more than private ;)Lyndseylyndsie
Zac: Just test the public/protected interface: This will not fail my test case early and I might be unable to test the class in a way I wanted it. Than I need to refactor the class, but in real life there might be much more behind, so that refactoring is not always possible, or possible at the moment. But this suggestion is an offtopic, since I wrote that I don't want to duplicate other questions dealing with this issue.Lyndseylyndsie
In addition to pimpl... To test a pimpl it is even not enough to include its implementation into the unit test implementing file, since pimple is a private type. Even than there is no way to access it...Lyndseylyndsie
This is the correct answer I'm afraid. The pimpl can be 100% public, defined in a private header file that never makes it to the outside of your class implementation (public headers shared with other programmers). Actually, 99% of the time a pimpl is a struct as all the members are public (although I don't personally implement them that way, but anyway...) This means all the tests have to do is access the data that's right there. Tweaking the main class (or interface) is not a good idea. You may want a test that stresses the main interface too, but without private members access.Helms
@AlexisWilke: Agreed, the pimpl's forward declaration doesn't have to be private. You can still do a header/cpp split on the pimpl, just put the header somewhere outside of the libraries public interface.Hottempered
@ovanes: I believe you are mixing test with production code - the only reason for the template declaration is for test - therefore it is test code. You even named it test_backdoor.Hottempered
@ovanes: If just testing the public interface is going to make the test failure too far from the problem, then your class is too big. But ultimately if you are unable/unwilling to refactor your code then there is only one standards compliant solution - the one you proposed.Hottempered
@Hottempered & AlexisWilke: Just curious... You need to write your own vector class, which is similar to the STL one. How would you test the size() function? How do you test the resize() function? An additional condition is: you are not allowed to access private members.Lyndseylyndsie
And an additional note: this vector should not give out the access to the underlying storage like it is possible with data(). Also the operator[] is not guaranteed to return the contained object by reference, instead it might use a proxy object, so that taking the address of the first element does not guarantee to return the memory address of the first element.Lyndseylyndsie
I realize that these are checking 2 functions at once, but without knowing how similar your vector is to a real std::vector I don't know of any better way. `pseudo_vector<int> v(0); v.push_back(42); assert( v.size() == 1 && "push_back() did not result in size change"); v.resize(10); assert( v.size() == 10 && "failed to resize" );'Hottempered
So, test the public interface, or put the functionality in a pimpl.Hottempered
@Zac: I proposed not to test two functions together, but writing tests for each of them. Finally, you are going to test the size function using another member function (push_back) which deals with private state as well. How do you know that push_back works? And I really mean: make unit tests in isolation. Now you proposed a solution with a bunch of dependencies between, push_back and size. Here if you insert an element to the vector and size returns the wrong number of elements where is the failure? In push_back or in size? pimpl won't help either.Lyndseylyndsie
@Ovanes, Comments are not good for making code examples. I thought it would be obvious that they should be separate test cases. If you are concerned about making test cases in isolation ( which is good ), then make a pimpl with no private members and write isolated test cases using the internals.Hottempered
S
3

What will follow is not technically speaking a straight answer to your question as it will still make use of the "friend" functionality but it does not require modification of the tested entity itself and I think it addesses the concern of breaking the encapsulation mentioned in some of the other answers; it does though require writing some boilerplate code.

The idea behind it is not mine and the implementation is entirely based on a trick presented and explained by litb on his blog(coupled with this Sutter's gotw for just a little bit more context, at least for me) - in short CRTP, friends, ADL and pointers to members (I must confess that to my dismay the ADL part I still don't get it entirely, but I'm relentesly working in figuring it out 100%).

I tested it with gcc 4.6, clang 3.1 and VS2010 compilers and it works perfectly.

/* test_tag.h */
#ifndef TEST_TAG_H_INCLUDED_
#define TEST_TAG_H_INCLUDED_

template <typename Tag, typename Tag::type M>
struct Rob
{
    friend typename Tag::type get(Tag)
    {
        return M;
    }
};

template <typename Tag, typename Member> 
struct TagBase
{
    typedef Member type;
    friend type get(Tag);
};


#endif /* TEST_TAG_H_INCLUDED_ */

/* tested_class.h */
#ifndef TESTED_CLASS_H_INCLUDED_
#define TESTED_CLASS_H_INCLUDED_

#include <string>

struct tested_class
{
    tested_class(int i, const char* descr) : i_(i), descr_(descr) { }

private:
    int i_;
    std::string descr_;
};

/* with or without the macros or even in a different file */
#   ifdef TESTING_ENABLED
#   include "test_tag.h"

    struct tested_class_i : TagBase<tested_class_i, int tested_class::*> { };
    struct tested_class_descr : TagBase<tested_class_descr, const std::string tested_class::*> { };

    template struct Rob<tested_class_i, &tested_class::i_>;
    template struct Rob<tested_class_descr, &tested_class::descr_>;

#   endif

#endif /* TESTED_CLASS_H_INCLUDED_ */

/* test_access.cpp */
#include "tested_class.h"

#include <cstdlib>
#include <iostream>
#include <sstream>

#define STRINGIZE0(text) #text
#define STRINGIZE(text) STRINGIZE0(text)

int assert_handler(const char* expr, const char* theFile, int theLine)
{
    std::stringstream message;
    message << "Assertion " << expr << " failed in " << theFile << " at line " << theLine;
    message << "." << std::endl;
    std::cerr << message.str();

    return 1;
}

#define ASSERT_HALT() exit(__LINE__)

#define ASSERT_EQUALS(lhs, rhs) ((void)(!((lhs) == (rhs)) && assert_handler(STRINGIZE((lhs == rhs)), __FILE__, __LINE__) && (ASSERT_HALT(), 1)))

int main()
{
    tested_class foo(35, "Some foo!");

    // the bind pointer to member by object reference could
    // be further wrapped in some "nice" macros
    std::cout << " Class guts: " << foo.*get(tested_class_i()) << " - " << foo.*get(tested_class_descr()) << std::endl;
    ASSERT_EQUALS(35, foo.*get(tested_class_i()));
    ASSERT_EQUALS("Some foo!", foo.*get(tested_class_descr()));

    ASSERT_EQUALS(80, foo.*get(tested_class_i()));

    return 0; 
}
Sharenshargel answered 24/7, 2012 at 0:5 Comment(0)
B
2

I am sorry to advice this, but it helped me when most methods in those answers are not achievable without strong refactoring: add before the header for the file with the class whose private members you wish to access,

#define private public

It is evil, but

  • doesn't interfere with production code

  • does not break encapsulation as friend / changing access level does

  • avoids heavy refactoring with PIMPL idiom

so you may go for it...

Brunabrunch answered 22/9, 2014 at 17:59 Comment(3)
This is one of the worst tricks available :) First of all this is non-starndard conform => you are not allowed to change language keywords. Second, it will break the ODR. For more read Herb Sutters GOTW on that.Lyndseylyndsie
@Lyndseylyndsie It might not break ODR. For example, if the class is header only and the only inclusion is from the unit test. Or if the #define is done as a compiler setting so everyone gets it, including the associated cpp and the unit test cpp.Soracco
Yes, I definitely know and understand that. But this is fragile code! Today the class is header only, tomorrow new developer comes to your team refactors the class and someone else spends hours finding the error. You are right with your statement: it MIGHT NOT break....Lyndseylyndsie
S
1

I don't usually feel the need to unit test private members and functions. I might prefer to introduce a public function just to verify correct internal state.

But if I do decide to go poking around in the details, I use a nasty quick hack in the unit test program:

#include <system-header>
#include <system-header>
// Include ALL system headers that test-class-header might include.
// Since this is an invasive unit test that is fiddling with internal detail
// that it probably should not, this is not a hardship.

#define private public
#include "test-class-header.hpp"
...

On Linux at least this works because the C++ name mangling does not include the private/public state. I am told that on other systems this may not be true and it wouldn't link.

Satire answered 12/7, 2012 at 18:31 Comment(8)
This is forbidden by the C++ standard to redefine language keywords. This may end up in ODR violation...Lyndseylyndsie
BTW, access specifier are not carried by C++ compiler until runtime.Lyndseylyndsie
@ovanes: Not necessarily: https://mcmap.net/q/15562/-c-preprocessor-define-ing-a-keyword-is-it-standards-conformingDetest
I would you point to this: gotw.ca/gotw/076.htm, So according to Herb Sutter you are a "Criminal: The Pickpocket". ;)Lyndseylyndsie
@ovanes: And you would be the "Language Lawyer." :-)Satire
Language issues aside, my fear with such would be that it would cause compilation issues (e.g., scoping ambiguities to appear, etc.) that otherwise would never occur. IMO unit testing code shouldn't even potentially break working code and redefining private to public risks such significantly --especially if the code is rich in type and scope usage.Sloop
For this trick to work on all systems you also want to include the .cpp file(s) in your test. #include "class.hpp" + #include "class.cpp" which then compiles as expected. Although I have never seen a problem with the result of such #define, I would indeed not recommend it... 8-)Helms
"BTW, access specifier are not carried by C++ compiler until runtime" -- That is not correct. If I try to access a private member, it is caught at compile time, not runtime. Although there may be some compilers adding code to catch runtime access (and I'd be surprised!) It would add code that is not (from what I know) in the C++ standard. Plus, I don't see why it would be necessary at runtime since it is perfectly checked at compile time (no risk of missing a forbidden call while compiling.)Helms
S
1

Testing private members is not always about verifying the state by checking if it equals some expected values. In order to accommodate other, more intricate test scenarios, I sometimes use the following approach (simplified here to convey the main idea):

// Public header
struct IFoo
{
public:
    virtual ~IFoo() { }
    virtual void DoSomething() = 0;
};
std::shared_ptr<IFoo> CreateFoo();

// Private test header
struct IFooInternal : public IFoo
{
public:
    virtual ~IFooInternal() { }
    virtual void DoSomethingPrivate() = 0;
};

// Implementation header
class Foo : public IFooInternal
{
public:
    virtual DoSomething();
    virtual void DoSomethingPrivate();
};

// Test code
std::shared_ptr<IFooInternal> p =
    std::dynamic_pointer_cast<IFooInternal>(CreateFoo());
p->DoSomethingPrivate();

This approach has the distinct advantage of promoting good design and not being messy with friend declarations. Of course, you don't have to go through the trouble most of the time because being able to test private members is a pretty nonstandard requirement to begin with.

Soliloquize answered 12/7, 2012 at 18:50 Comment(4)
mmmm..... What if I do generic programming and don't have virtual functions at all??? I think this approach violates LSP and somehow I even did not get what is the intention of it. What is tested here? Foo or the CreateFoo factory?Lyndseylyndsie
Obviously this code does not apply to the case where you purposely avoid virtual behavior. To fix the alleged LSP violation, one could do multiple inheritance; I only avoided it here for simplicity. Clearly, Foo is tested here, it just happens to be produced by a mini factory function (probably just doing return std::shared_ptr<IFoo>(new Foo);).Soliloquize
A small tip ;) Consider using std::make_shared instead of new Foo... en.cppreference.com/w/cpp/memory/shared_ptr/make_shared Here is why: herbsutter.com/gotw/_103Lyndseylyndsie
I like your solution but I feel this method is more complicated than the OP's solution to understand and maintain. IMO it would be easier to just have a friend function since it doesn't hide code from non-testers. Potentially not good is that the inheritance changes the memory layout of involved classes especially if multiple inheritance has to be done and that might be a deal-breaker.Sloop
E
1

I used a function to test private class members which was just called TestInvariant().

It was a private member of the class and, in debug mode, was called at the beginning and end of every function (except the beginning of the ctor and end of the dctor).

It was virtual and any base class called the parent version before it's own.

That allowed me to verify the internal state of the class all of the time without exposing the intenals of the class to anyone. I had very simple tests but there is no reason why you could not have complicated ones, or even set it on or off with a flag etc.

Also you can have public Test functions which can be called by other classes which call your TestInvariant() function. Therefore when you need to change inner class workings you do not need to change any user code.

Would this help?

Euniceeunuch answered 20/7, 2012 at 15:12 Comment(2)
Note, that you are exposing the internals to others - if the TestInvariant() methods were used for checking internal state via code in a subclass then the state has to be protected. If the state is protected then you have exposed it to any subclass. Inheritance is one of the tightest forms of coupling.Hottempered
But if you made the testinvariant functions protected couldn't the data stay private?Euniceeunuch
O
1

I think the first thing to ask is: Why is friend considered to be something that must be used with caution?

Because it breaks encapsulation. It provides another class or function with access to the internals of your object, thus expanding the visible scope of your private members. If you have a lot of friends, it's much harder to reason about the state of your object.

In my opinion, the template solution is even worse than friend in that regard. Your main stated benefit of the template is that you no longer need to explicitly friend the test from the class. I argue that, on the contrary, this is a detriment. There are two reasons for that.

  1. The test is coupled to the internals of your class. Anyone changing the class should know that by changing the privates of the object that they may be breaking the test. friend tells them exactly what objects might be coupled to the internal state of your class, but the template solution doesn't.

  2. Friend limits the scope expansion of your privates. If you friend a class, you know that only that class may access your internals. Thus, if you friend the test, you know that only the test can read or write to private member variables. Your template back door, however, could be used anywhere.

The template solution is ineffective because it hides the problem rather than fixing it. The underlying issue with the cyclic dependency still exists: someone changing the class must know about every use of the back door, and someone changing the test must know about the class. Basically, the reference to the test from the class was removed only by making all private data into public data in a roundabout way.

If you must access private members from your test, just friend the test fixture and be done with it. It's simple and understandable.

Oribelle answered 21/7, 2012 at 9:8 Comment(0)
S
0

There's a theory that if it's private it shouldn't be tested alone, if it needs so then it should be redesigned.

For me that's Shi'ism.

On some project people create a macro for private methods, just like:

class Something{
   PRIVATE:
       int m_attr;
};

When compiling for test PRIVATE is defined as public, otherwise it's defined as private. that simple.

Sands answered 20/7, 2012 at 17:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.