Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing C With Functions Not in Header

I'm starting to get into unit testing and I'm having trouble understanding something. My struggle boils down to how I would go about testing functions that are only in the .c source, and not declared in the .h header. There's certain functions that shouldn't need to be called outside of the implementation because they're only relevant to that specific file. Since they're not visible to other parts of the program, that means my unit testing cases file can't see those inner functions, therefore making me unable to test them. I've gone around the issue by using forward declarations in the test cases file, but that seems kinda messy and would get to be a pain to go and change if I modify the function parameters.

Are these functions just not meant to be covered by unit testing? I've read that with OOP, you shouldn't be testing private functions because they're tested implicitly through the public functions, but it feels uncomfortable to not have these functions covered (some of which can get quite complex).

like image 456
Henry Bergin Avatar asked Oct 02 '17 18:10

Henry Bergin


People also ask

Should you write unit tests for every function?

The answer to the more general question is yes, you should unit test everything you can. Doing so creates a legacy for later so changes down the road can be done with peace of mind. It ensures that your code works as expected.

Which testing is not included in unit testing?

A test is not a unit-test if: it communicates with a database. it cannot run in parallel with other tests.

Should unit test cover all cases?

It isn't realistic -- or necessary -- to expect 100% code coverage through unit tests. The unit tests you create depend on business needs and the application or applications' complexity. Aim for 95% or higher coverage with unit tests for new application code.


2 Answers

Blackbox testing is about testing the software contract between your publicly visible interface and the users. To test this, one typically creates a set of test cases using a tool or a separate test program, that #include's your header file .h which defines your external interfaces. Sounds like you already have this. Great!

What is missing is the concept of White Box testing. This is every bit as important as black box testing for many industries such as telecom, railways, aerospace, or any other industry where high availability and quality need to be assured to a high degree.

For White Box testing, create a separate "private" interface that is used only by your White Box test program. Notice, that in C you can create multiple header files for a given C implementation file. From the compiler's perspective, there is no real enforcement of the number of headers or their names. It is best to stick to a convention as dictated by your project or team.

For our projects, we create a public header (with a simple .h suffix) for our external interfaces, and private header (_pi.h) for our private interfaces intended for a select few who need to access private interfaces such as white box testing, auditing data structures, internal provisioning and diagnostics, and debug tools. Of course, the _pi.h suffix is just a convention, but it works well in practice.

White Box testing is very useful to test your internal functions and data structures, and can go well beyond the limits of Black Box testing. For example, we use White Box test cases to test internal interfaces, and to see what happens when our data structures get corrupted, as well as corner cases such as testing what our code does when passed an unexpected parameter value internally.

For example, let's say we have a file called foo.c that we wanted to perform white box testing on. Then we would create two headers: foo.h and foo_pi.h for external and internal users respectively.

File foo.h

#ifndef FOO_H
#define FOO_H

typedef int FooType;

// Public header for Foo
void Foo(FooType fooVal);
void Bar(void);

#endif

File foo_pi.h

#ifndef FOO_PI_H
#define FOO_PI_H

// PI should also include the public interface
#include "foo.h"

// Private header for Foo
// Called by White Box test tool 
void FooBar_Test1(FooType fooVal);
void Foo_Internal(void);
void Bar_Internal(void);

#endif

File foo.c

#include "foo.h"
#include "foo_pi.h"
// Notice you need to include both headers

// Define internal helpers here
static FooType myFooVal = 0; 
void FooBar_Test1(FooType fooVal) {myFooVal = fooVal;}

void Foo_Internal() {Bar_Internal();}
void Bar_Internal(void) {myFooVal++;}      


// Define external interfaces after the helpers
void Foo(FooType fooVal) {myFooVal = fooVal; Foo_Internal();}
void Bar(void)           {Bar_Internal();}

// Main() not typically included 
// if this is just one module of a bigger project!
int main(int argc, char** argv)
{
 Foo(argc);
}

If you are confused what all those #ifndef/#define/#endif things are, these are CPP macros, and this use is not enforced in C but it is a widely used convention. For more details, see https://stackoverflow.com/a/42744341/6693299

While I did not show it in the example above, the Foobar_test() routine (and any other internal test methods, would typically be placed in a separate module reserved for internal test functions. Then they can be packaged out of the final product. Along with some fancy CPP preprocessing that I won't describe here, you can conditionally-compile out the private headers and the test functions, and make the interface secure for the production load (after White Box testing is done). But that is probably too much detail!

like image 147
ScottK Avatar answered Sep 24 '22 10:09

ScottK


@ScottK's description of Blackbox versus Whitebox testing is useful. My approach for Whitebox testing is different.

Depending on the project structure, I either

  • put the unit tests in the same file as the unit under test, usually at the end of the file, all inside a #if UNIT_TEST ... #endif

  • #include the C file under test in the unit test code; this provides access to all the internal functions without any change to the original source code

like image 26
Doug Currie Avatar answered Sep 22 '22 10:09

Doug Currie