My team frequently creates wizards that run in Windows PE as part of a Configuration Manager task sequence, and we write them in C++. I’m a big fan of TDD, so when I started to work on a new architecture for our wizards, I wanted to write all the code using TDD.
When I asked around about writing C++ tests, I was told there isn’t any support for C++ unit tests in Visual C++. Not true. In this blog post I’ll describe what’s required in order to write unit test for native C++ code using Visual Studio.
For new C++ projects, you can set them up as I’ll describe below. However, for your existing code, you may have to reorganize your project structure before you can start writing unit tests.
Here is a quick overview of the setup for testing C++ code:
- The unit tests need to be written in C++/CLI in order to use the .NET unit test framework
- The code you’re testing should be in a static library that is linked into both your production EXE/DLL, as well as the test DLL
Setting up the projects
As I mentioned above, the first step is to setup the projects correctly:
- Create a new Win32 Project and select Static library in the Application Settings screen. This is where you’ll add your production code
- Create a second Win32 Project for your production EXE or DLL
- Create a Test Project (in the C++ section of the Add New Project dialog box)
- In the Properties dialog box for the test project, change the Target Name to the name you want to use for you unit tests. The default is always DefaultTest, rather than the name of the project you just created
I’m using Visual Studio 2010, but these instruction will most likely work with prior versions, especially Visual Studio 2008.
Next we need to link the static library into the test and production projects, and provide both of those projects access to the header files in the static library:
- Right click the test project and select Properties
- In the Configuration combo box, select All Configurations to ensure the changes below are made to both Debug and Release configurations
- Click the Common Properties node and then Framework and References
- Click Add New Reference…, select the static library and click OK
- Click the Configuration Properties node, then click the C/C++ node
- Click in the Additional Include Directories property and type something like this (substitute the name of your library project for ProductLibrary):
- Change the Common Language RunTime Support property to Common Language RunTime Support (/clr)
You’ll want to repeat steps 1-6 for your EXE/DLL project.
Writing C++/CLI Unit Tests
At this point you should be able to build your solution, so the next step if you’re using TDD is to write a test method. If you’ve written test methods in C#, most of this should be familiar to you, although with a different syntax. But if you’re a C++ programmer and you’ve never written C# unit tests, there is a bit of a learning curve. Here I’ll explain enough to get you started.
Creating a new C++ Unit Test project initially creates a file called UnitTest1.cpp that contains one unit test. I usually delete the extra code and just leave the single test method. If you have a simple class called SomeClass with a method called SomeValue that returns an int, you can write a test that looks like this:
std::unique_ptr<SomeClass> pClass(new SomeClass());
There are several elements to this test that you may not be familiar with, but you can find full details on MSDN. First is the TestMethod attribute. This identifies the method as being a test method. When you run the tests in Visual Studio, it will run any public methods marked with this attribute.
Next, notice the use of the Assert class static method called AreEqual. There are a number of static methods that allow you to validate the results of running a command. Again, you’ll find all of these documented on MSDN.
Some Tips and Tricks
The most significant limitation is the lack of IntelliSense in C++/CLI in Visual Studio 2010 (the C++ team plans to correct that in a future release). Fortunately, there is a third-party product called Visual Assist X from Whole Tomato Software that brings back IntelliSense to C++/CLI in Visual Studio 2010. I’ve been using this now for a few weeks and I really love it.
There are some tricks you’ll want to be aware of when you’re using Assert. Because Assert is designed for .NET code, there are some methods that won’t apply, such as IsNotNull, which expects to receive a managed pointer rather than a native pointer. Here are some ways you can deal with this limitation:
Assert::AreNotEqual<DWORD_PTR>(0, (DWORD_PTR) pClass, "Should not be null");
Assert::AreEqual<DWORD_PTR>(0, (DWORD_PTR) pClass, "Should be null");
Assert::IsTrue(pClass == nullptr, "Should be null");
Finally, if you want to test a string value returned by a function, you can do something like this:
Assert::AreEqual<String^>("Expected", gcnew String(pClass->StringValue());
Note in particular the use of gcnew, which creates a new instance of a managed class. Likewise, you’ll notice the ^ at the end of String in the AreEqual. This tells the compiler that we’re working with a pointer to a managed instance of type String.
I’ve create a new blog post with more tips and tricks: C++/CLI to C++ Tips and Tricks.
For code coverage, see Capturing C++ Code Coverage with Visual C++.
I’ve been using C++/CLI unit tests with native C++ code now for about 1-1/2 months and I find it a really nice environment. I can debug my unit tests just as easily as C# unit tests. At this point I have about 240 tests and they run in about a 1-2 seconds, which means I can easily run all these tests after making changes to ensure I haven’t broken anything.
After so many years writing in C#, I never thought I would enjoy C++ programming again. I was wrong. Using TDD to write C++ code is almost as nice as writing C# code, and I’m really enjoying the experience.
I want to thank my colleague Mike Schmidt for getting me pointed in the right direction. He had some C++/CLI unit tests, but they were testing public functions of a DLL. I did some research and added the part of about using a static library, which provides full access to the internals of the code—just the thing you need for writing code with TDD.