C++ has acquired capabilities related to inheritance throughout its rich history that are not available in high-level languages like C# or Java (although simplified design avoids issues like the diamond problem; however, the diamond problem can still occur with interfaces in Java. But our topic today is testing, not language design).
These capabilities include multiple inheritance, public/protected/private inheritance modes, friend classes, the ability to change accessibility in derived classes, and more.
Let’s start with inheritance theory in C++. When inheriting a base class, C++ allows you to control the access level of the base class’s members in the derived class by specifying the inheritance mode (public, protected, or private).
Public inheritance mode
The public members of the base class remain public in the derived class. The protected members of the base class remain protected in the derived class, meaning they are accessible within the derived class, but not outside it (i.e., they are not accessible by objects of the derived class or other external code). The private members of the base class are not accessible in the derived class at all.
#include <iostream>
class Base {
public:
void PublicMethod() {std::cout << "Public\n" ;}
protected:
void ProtectedMethod() {std::cout << "Protected\n";}
private:
void PrivateMethod() {std::cout << "Private\n";}
};
class Derived : public Base {
public:
void SomeTest() {
PublicMethod(); // allowed
ProtectedMethod(); // allowed
PrivateMethod(); // is not allowed
}
};
int main(int argc, const char * argv[]) {
Derived derived ;
derived.SomeTest();
derived.PublicMethod(); // allowed
derived.ProtectedMethod(); // is not allowed
derived.PrivateMethod(); // is not allowed
return 0;
}
For the protected inheritance mode
Public and protected members of the base class become protected in the derived class. Private members remain inaccessible.
#include <iostream>
class Base {
public:
void PublicMethod() {std::cout << "Public\n" ;}
protected:
void ProtectedMethod() {std::cout << "Protected\n";}
private:
void PrivateMethod() {std::cout << "Private\n";}
};
class Derived : protected Base {
public:
void SomeTest() {
PublicMethod(); // allowed
ProtectedMethod(); // allowed
PrivateMethod(); // is not allowed
}
};
int main(int argc, const char * argv[]) {
Derived derived ;
derived.SomeTest();
derived.PublicMethod(); // is not allowed
derived.ProtectedMethod(); // is not allowed
derived.PrivateMethod(); // is not allowed
return 0;
}
For private inheritance
Public and protected members of the base class become private in the derived class.Private members remain inaccessible.
#include <iostream>
class Base {
public:
void PublicMethod() {std::cout << "Public\n" ;}
protected:
void ProtectedMethod() {std::cout << "Protected\n";}
private:
void PrivateMethod() {std::cout << "Private\n";}
};
class Derived : private Base {
public:
void SomeTest() {
PublicMethod(); // becomes private and is allowed
ProtectedMethod(); // becomes private and is allowed
PrivateMethod(); // is private and is not allowed
}
};
int main(int argc, const char * argv[]) {
Derived derived ;
derived.SomeTest();
derived.PublicMethod(); // is not allowed
derived.ProtectedMethod(); // is not allowed
derived.PrivateMethod(); // is not allowed
return 0;
}
In addition, one interesting possibility is that we can change accessibility level with ‘using’ keywork (whic is oftern referenced as using-declaration in the C++ standard) in the following way (see https://en.cppreference.com/w/cpp/language/using_declaration for details)
#include <iostream>
class Base {
public:
void PublicMethod() {std::cout << "Public\n" ;}
protected:
void ProtectedMethod() {std::cout << "Protected\n";}
private:
void PrivateMethod() {std::cout << "Private\n";}
};
class Derived : private Base {
public:
using Base::PublicMethod;
using Base::ProtectedMethod;
void SomeTest() {
PublicMethod(); // now are public
ProtectedMethod(); // now are public
PrivateMethod(); // is private and is not allowed
}
};
int main(int argc, const char * argv[]) {
Derived derived ;
derived.SomeTest();
derived.PublicMethod(); // is allowed
derived.ProtectedMethod(); // is allowed
derived.PrivateMethod(); // is not allowed
return 0;
}
Let’s get back to faking methods in unit testing. The typical approach is to inherit from the original class with a fake derived class and override the methods in the derived class. If a method in the original class is private or protected, C++ allows you to change its accessibility by inheriting from the original class and declaring the methods you need to fake as public.
Even though methods in the base class are not virtual, this does not disallow you from redeclaring them as public in the derived class and writing your test-related logic, something that is disallowed in other languages. In C++, RTTI and virtual methods are responsible for dynamically choosing the right method to call (when using a pointer/reference to an object) during runtime, based on the class instance information, while inheritance is other area.