Avoiding the redundancy of adding the object files to both the primary project and its unit test

Raymond Chen

A customer was hoping for a simpler way to structure their unit tests: They had a Visual Studio solution with two projects, Contoso and ContosoUnitTest. Every time they add a new cpp file to Contoso, they have to add a reference to that same cpp file to the ContosoUnitTest.

Is there an easier way to do this? Maybe a way to set a wildcard in the ContosoUnitTest project so it just slurps up all of the cpp files in the Contoso directory?

(This problem and its solution applies to any build system, but I chose Visual Studio for concreteness.)

I don’t think there’s a wildcard option in Visual Studio,¹ but even if it did, I don’t feel comfortable using it, because it means that any random file not part of the project that happens to be created in the directory will get scooped up into the unit test project.

As with many problems in computer science, this can be solved by adding another level of indirection. In this case, what you do is create a new project called ContosoCore or ContosoLib or something like that. This project includes all the cpp files and produces a library file. You then consume that library from the Contoso and ContosoUnitTest projects.

When you add a new cpp file to your project, add it to the ContosoLib project, and that will add it to both Contoso and ContosoUnitTest.

This also has the benefit that the cpp files are compiled only once (by ContosoLib), and the compiled cpp files are then consumed by the production Contoso project and the ContosoUnitTest.

¹ Current documentation says that Visual Studio IDE doesn’t support wildcards.

9 comments

Discussion is closed. Login to edit/delete existing comments.

  • Neil Owen 4

    “I don’t think there’s a wildcard option in Visual Studio,¹ but even if it did, I don’t feel comfortable using it, because it means that any random file not part of the project that happens to be created in the directory will get scooped up into the unit test project.”

    I feel like this is one of those stockholm syndrome statements which gets tossed around to justify a missing feature. Used to hear the same thing about C#, but when csproj files added support for wildcards a few years back they were quickly and nearly universally adopted. Less merge conflicts, less errors from missing files in one place or another, it’s an overall much improved experience. They also shrunk down the project files to be much more understandable, so now they are usually small and only contain information a user would reasonably want to control.

    If you have random code files (with the right extensions) floating around unused, they are likely a problem anyway. What files are included in source control is a pretty important thing to think about in general, and having to duplicate that decision between your source control system and the project file is just an unneeded source of errors.

    • Antonio Rodríguez 0

      Note that Raymond is saying *he*, personally, wouldn’t be comfortable with it (“I don’t feel comfortable using it”, he said). As he has told many times, this blog represents his personal views and opinions, which may be different from Microsoft’s official statements.

      That said, I agree with both you and Raymond. How can it be? Wildcards in projects can be a double edged sword, which depends on how you use it. You are right in that if the source three is well maintained, they should pose no problem and make things easier and more manageable. But I’m also a reader of The Daily WTF, as Raymond is, and I know that in many cases projects are less than tidy. Sometimes much, much less. You can find, for example, a file named “libinitech.c”, alongside with “libinitech.old.c”, “libinitech.bugfix.c” and, maybe “libinitech.contoso.c”. In those cases, a wildcard is a nice gun with which you can easily shot your foot.

      • Joshua Hudson 0

        Converter bot to the rescue.

        Clearly the upgrade solution is to evaluate the project file and the wildcard expansion and emit exclude rules for all source files that aren’t in the project today.

        On a related note I use makefiles a lot; and I avoid the merge problem by having one makefrag per directory that gets included into the toplevel makefile. It’s also possible to use wildcards in makefiles; which I have gone back and forth about using a few times. When I have really small related projects (one or two source files) I like to overlay three or four of them in the same directory.

        On a totally different related note; you can also use a dll rather than a lib. Back when disk read times were long I preferred to have almost everything in a dynamically loaded DLL (as in the EXE does LoadLibrary & GetProcAddress) so that I can put the splash screen up before loading in most of the code.

        • Alex Martin 0

          Back when disk read times were long I preferred to have almost everything in a dynamically loaded DLL (as in the EXE does LoadLibrary & GetProcAddress) so that I can put the splash screen up before loading in most of the code.

          Executable mappings are demand-paged, though, right? The kernel loads and relocates pages as they’re first hit?

  • Almighty Toomre 4

    One solution that I’ve used is to make a “shared project” (it’s a project type) and reference it from both the main and unit test projects.

    When you add new files individually to the shared project, they are automatically picked up by all projects that have the shared project as a reference. And unlike the “make a library” option, you can have different #define values so the code can be compiled differently.

    Every new solution I’ve created since I learned about them has been organized with shared projects.

    • Georg Rottensteiner 1

      Same here. “Shared Projects” are a godsend. I also like to use them when building for different targets.

      There’s the main game logic shared project (which also houses the assets), and the final target specific main file (Win32, SDL, UWP, Emscripten, whatever). Works a treat!

      And I’m absolutely no fan of wild cards. Neither in Visual Studio nor with makefiles. It’s a chore to find out why a specific file is included when you don’t want to and vice versa. With a proper solution, every file that’s listed there (and nothing else) is compiled. Simple as that.

    • iain clarke 0

      Nice! That’s a new one for me. It surely would lose any build time benefits over making a Lib file though? That’s little benefit when building a shippable release, but it would make testing happen less often if the delay in building makes it a nuisance.

  • Ian Boyd 0

    My solution is to add an option to run unit-tests from the application itself.

    This has the added virtue that you can also have integration and end-to-end tests on the client PC.

    • Alex . 0

      This requires the target binary to run on the dev machine which isn’t trivial in the general case (dependencies…) and also way slower when the CPU architectures don’t match. You can build and fire up a VM with all the needed stuff included but it hurts your feedback loop.

      We build selected parts of our (legacy) code for the host in the unit test project and run it under valgrind which also adds some value. It’s fast and actionable as the debug symbols are always available, unlike on the target.

Feedback usabilla icon