32

We develop a C++ application using Visual Studio 2008 and unit test using Boost.Test. At the moment, we have a separate solution which contains our unit tests.

Many of our projects in the core solution produce DLL's. We're limited in test coverage because we cannot test non-exported classes.

I have two ideas on how these could be tested:

  1. Export everything
  2. Put the tests inside the DLL (same project and solution) and use Boost.Test's external runner

I'm not entirely sure what the drawbacks would be. Number 1 above breaks module level encapsulation, and number 2 could result in a much larger DLL, unless it's possible to only include the test code in certain configurations.

So, are there any severe drawbacks to the above methods, or can you think of other solutions?

Jon
  • 9,815
  • 9
  • 46
  • 67
  • 7
    I would like to hint at [CMake](http://www.cmake.org) offering a feature called "object libraries". (`add_library( foo_obj OBJECT ... )`) In my projects I build the sources into object libraries, which I then link into *both* the DLL (`add_library( foo SHARED ... $ )`) *and* its test drivers (`add_executable( foo_test ... $ )`). It's a variant of the answers below using a different build system (which is why I added this as a comment, not an answer), but it's solving the same problem. – DevSolar Aug 25 '15 at 12:47

4 Answers4

16

Expanding on Tom Quarendon's answer to this question, I have used a slight variant of Simon Steele's response:

  • Create a test project (using whatever test framework you like, I use CppUnit).
  • In your test_case.cpp, #include <header/in/source/project.h>.
  • In the test project properties:
    • In Linker->General, add the source project's $(IntDir) to the Additional Library Directories.
    • In Linker->Input, add the .obj files to the Additional Dependencies.
  • Add the dependency from the test project to the source project in Project->Project Dependencies.

Again, the only maintenance overhead is the standard one for unit tests - to create the dependency on the unit(s) you want to test.

idmean
  • 14,540
  • 9
  • 54
  • 83
Rai
  • 1,328
  • 11
  • 20
  • 4
    Another detail about this approach: If your DLL under test uses pre-compiled headers, all of the .obj files that are linked to produce the DLL under test depend on the pre-compiled header file from the DLL under test. When building the Test project, this results in linker error LNK2011: precompiled object not linked in; image may not run. In addition to the specific object files you are testing, you must add stdafx.obj (if your PCH file is generated by compiling stdafx.cpp). – Tim Crews Sep 05 '15 at 21:27
  • Another note on this approach: If your test project uses multiple dlls that contain identically named object files that need to be tested, you can specify the full path in the Linker->Input settings, e.g. `$(SolutionDir)\t\$(Platform)\$(Configuration)\.obj` to distinguish between them. – Edward May 12 '16 at 09:28
  • You might also need [/FORCE:MULTIPLE](https://msdn.microsoft.com/en-us/library/70abkas3.aspx) as I just discovered. – Rai Sep 30 '16 at 12:23
  • To avoid having to type all file names out, one can instead do it in the command line additional options using a wildcard, as in https://stackoverflow.com/a/25776187/640195 – yoel halb Mar 24 '19 at 20:27
  • Why should I have the obj not the lib? what are the differences here? because it only works with .obj, not with .lib – Mohammed Noureldin Jun 14 '19 at 13:44
  • What is `$(IntDir)` of the source project? I don't have any `IntDir` folders in my source project, only Debug. – KulaGGin Jun 22 '20 at 15:13
  • @KulaGGin: https://learn.microsoft.com/en-us/cpp/build/reference/common-macros-for-build-commands-and-properties?view=vs-2019 – Rai Jun 22 '20 at 15:20
  • @Rai Thanks. So in my UnitTest project, how do I set it? Here's how I set it now: https://i.imgur.com/GZyh3OB.png . I used Visual Studio's default Native Unit Test project. It still doesn't work and I still get the error about that it can't open .lib file: https://i.imgur.com/mxI8dw6.png, I did everything else described in the answer. Here's my barebones solution with the problem: https://github.com/KulaGGin/TestLibrary – KulaGGin Jun 22 '20 at 16:07
  • @KulaGGin I don’t see that you’ve set anything on your Additional Library Directories. The Microsoft documentation should help with solution configuration. – Rai Jun 22 '20 at 17:55
  • @Rai I added Linker->General directory and also added .obj files in Linker->Input. I pushed the solution with the problem again. Please, check again. I still get the problem and I don't understand how to fix it. – KulaGGin Jun 22 '20 at 22:00
4

The solution I use for this is to build the same non-exported code into my tests DLL as well. This does increase build time and means adding everything to both projects, but saves exporting everything or putting the tests in the main product code.

Another posibility would be to compile the non-exported code into a lib which is used by both the DLL with exports, and the unit test project.

Simon Steele
  • 11,558
  • 4
  • 45
  • 67
  • 1
    This could work for small projects, but we have a lot of code, so it would be a maintenance nightmare to have to make changes in two places. – Jon Mar 31 '11 at 08:24
  • 1
    The only changes that would need to be made though is when files are added or removed. So if a new CPP file is added containing code that needs to be unit tested, then it needs to be added to both projects. There aren't two copies of the source code, each source file containing testable code is just included in both projects. – Tom Quarendon Jun 26 '13 at 09:22
  • This was my first approach, which worked for me. I thought of a potential problem with it though - sometimes a dll project uses different compile flags than the test project. Therefore, the dll and test projects could create different object files for the same source file. Though in my case I was pretty sure they were the same, in general it's safer to test the object files that the dll project creates, rather than the object files that the test project creates. I ended up switching my approach to the one @Rai described. – Avi Tevet Oct 05 '17 at 18:24
2

Was searching a solution as well, maybe the following will be easier to maintain.

Add a new build configuration e.g. "Unit testing Debug" to the DLL project and change the Configuration Type to be "Static Library .lib" ("General"->"Configuration Type").

Then just add a dependency of your unit tests on this project, now everything should link together when you use new build configuration "Unit testing Debug". If you are using release builds for unit tests then you need to add another configuration with release optimizations.

So the benefits of this solution are:

  • low maintanability cost
  • single DLL/Static library project
  • don't have to manually link to .obj files

Drawbacks:

  • Extra configuration profile(s) will require some changes in your build environment (CI)
  • Greater compilation times

Update: We actually ended up using a different approach.

We added new "Test debug"/"Test release' configurations for every existing project that we have.

For .exe/.dll projects we disable the original main.cpp from compiling and replaced it with the one that instantiates the test framework (e.g. gtest) and runs all the tests, the tests are in separate .cpp files which are also excluded from compilation in regular configurations (Release/Debug) and enabled only in Test configurations.

For .lib projects we also have new "Test debug"/"Test release" configurations and there we convert the static library to be an .exe file and provide a main.cpp which instantiates the testing framework and runs the tests and tests themselves. Test related files are excluded from compilation on Release/Debug configurations.

AndreiM
  • 113
  • 2
  • 6
0

Try making a define such as the following somewhere all files will include:

#define EXPORTTESTING __declspec(dllexport)

And use it in place of the dllexport, like this:

class EXPORTTESTING Foo 
{
 ...
};

Then you will be able to turn off the flag for building a release DLL, but keep it on for a unit-testable DLL.

Janik Zikovsky
  • 3,086
  • 7
  • 26
  • 34
  • 3
    Not sure it's a good way to do like that... The testable code should not be modified to be tested. Even if it's a simple macro. – toussa Jan 02 '14 at 14:11