5

I'm writing a simple whitebox unit testing suite for a project I'm working on in C. The project is broken into modules (each has a .c file with an associated .h file), and all modules are compiled together into one .so file.

Obviously, the .h file for each module contains function prototypes for any function that is meant to be visible to the other modules, but does not contain prototypes for internal or helper functions.

My test suite is a collection of .c files in a separate directory. Each .c file is responsible for testing a specific module, plus a master file which executes all the tests. All of these files are compiled into one executable. Currently this binary is linked to the .so binary of the project, and I'm #includeing the project's .h files in the individual module testing files as appropriate. This allows me to test all of the "public" functions.

I would also like to directly test some of the internal and helper functions. I can think of a few ways to go about this.

  1. Go ahead and make all of a module's functions defined in that module's header file. I don't want to do this because it seems like bad practice.
  2. Create a second collection of .h files which contain the prototypes for all the internal/helper functions, and include these in the appropriate testing files. I'm hesitant to do this because it seems like a lot of work to maintain.
  3. #include the module's .c file directly. This would be easy to maintain, but it would make me feel so dirty inside.

Which (if any) of these techniques are the best way to go about this?

  • 2
    I would argue that you shouldn't unit test internal functions - see also Unit testing internal components. In theory, internal functions can be added, removed, or rewritten at will without out harming anything, provided the external functions continue to pass their unit tests. – Dan Pichelman Feb 2 '16 at 17:04
  • @DanPichelman personally, i do agree with you. my team does not. they've mandated a static code coverage target. – Woodrow Barlow Feb 2 '16 at 17:05
  • 1
    If you're testing all your external functions, doesn't that imply that they're calling all your internal functions? If not, why are those functions there? – TMN Feb 3 '16 at 20:19
2

Have you considered putting the functions into the module's header file, but putting them in a conditionally compiled block that is only enabled in build configurations that are tested? It would be analogous to having a C++ class with public and private functions. However, that's not a good option if you're shipping the header file to users, and want to keep those functions secret.

Including the C file directly is a very dirty option, as it might not get built exactly the same as it would in the "real" code, and can also cause build problems when the functions that are in the header are implemented multiple times (in the library and the test code). That said, I've resorted to doing such things at times. When things aren't designed to be tested from the beginning, sometimes you need to get a little dirty.

  • 1
    i'm going to accept this answer because it best answers the question as asked, even though, as others have pointed out, testing internal functions may not be best best practice. – Woodrow Barlow Feb 4 '16 at 17:07
5

Unit tests are supposed to be for external functions only.

Nevertheless, if you have a lot of "internal code", it should be tested too right? So how?

Make smaller functions, separated from you code (another .c and .h), and use those functions as a library that can be unit tested. Then you should be able to test those functions that are used by the internal ones from the external API without any problem. What you cannot test is how you use those functions. But the behavior of those functions should cause something which can be tested on the external functions, otherwise, why are they there?

That is easy to maintain. A pain to do it if you have a lot of code already written, but doable.

0

The thing with any form of testing is that in order for it to be worthwhile, when it finds defects, you have to fix them. While it is very easy to instead change your tests to match the buggy behavior, this invalidates the whole exercise.

In this case, your testing has found defects: your internal functions have paths that are not reachable from tests of the external API. Perhaps they are just redundant null checks, perhaps you are missing some higher-level test cases, perhaps the internal logic implements a more general abstraction than actually used.

Either way, the right approach is to fix them. Null checks that can't trigger should be assertions (which should be excluded from code coverage), missing test cases should be written, internal abstractions should be in their own file.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.