Maybe you read Part 1 of this article. If you did you’ll know it concerns adding tests to legacy code (legacy code is code without tests). You will also know that the code has file scope functions and data that we want to test directly.
My opinion on accessing private parts of well designed code, is that you do not need to. You can test well design code through its public interface. Take it as a sign that the design is deteriorating when you cannot find a way to fully test a module through its public interface.
Part 1 showed how to #include
the code under test in the test file to gain access to the private parts, a pragmatic thing to do when wrestling untested code into a test harness. This article shows another technique that may have an advantage for you over the technique shown in Part 1. Including the code under test in a test case can only be done once in a test build. What if you need access to the hidden parts in two test cases? You can’t. That causes multiple definition errors at link time.
This article shows how to create a test access adapter to overcome that problem.
We’d like to get IsLeapYear()
under test. IsLeapYear()
is a static function from Date.c
introduced in Part 1. I’d like to write this test, but IsLeapYear
is hidden, so we get compilation and/or link errors.
TEST(Date, regular_non_leap_year)
{
CHECK_FALSE(IsLeapYear(1954));
CHECK_FALSE(IsLeapYear(2013));
}
You get compilation errors, because there is no function declaration in Date.h
for the hidden functions and data. If you overcame those errors by adding declarations, in the test file or tolerating the related warnings, you would be rewarded with unresolved external reference errors by the linker.
The solution is pretty straight-forward; write the test using a test access adaptor function:
TEST(Date, regular_non_leap_year)
{
CHECK_FALSE(CallPrivate_IsLeapYear(1954));
CHECK_FALSE(CallPrivate_IsLeapYear(2013));
}
CallPrivate_IsLeapYear()
is a global function declared in DateTestAccess.h like this:
#ifndef DATE_TEST_ADAPTOR_INCLUDED
#define DATE_TEST_ADAPTOR_INCLUDED
#include "Date.h"
bool CallPrivate_IsLeapYear(int year);
#endif
CallPrivate_IsLeapYear()
is implemented in DateTestAccess.c
like this:
#include "Date.c"
bool CallPrivate_IsLeapYear(int year)
{
return IsLeapYear(year);
}
You could add similar accessors for private data to DateTestAccess.h
#ifndef DATE_TEST_ADAPTOR_INCLUDED
#define DATE_TEST_ADAPTOR_INCLUDED
#include "Date.h"
bool CallPrivate_IsLeapYear(int year);
const int * GetPrivate_nonLeapYearDaysPerMonth(void);
#endif
Implemented like this:
const int * GetPrivate_nonLeapYearDaysPerMonth(void)
{
return nonLeapYearDaysPerMonth;
}
There are some variations of this that could be helpful. If the code under test has problem #include
dependencies, you could #define
symbols that are needed in DateTestAdaptor.c
and also #define
the include guard symbol that prevents the real header from being included.
I don’t love doing any of this, except when I do. That is limited to when it solves the problem of getting hidden code under test without modifying the code under test. A plus is that the code under test does not know it is being tested, that’s a good thing. Neither of these approaches are long term solutions. They are pragmatic steps toward getting code under tests so that it can be safely refactored and have new functionality test-driven into it.