Writing Unit Tests in Visual Studio for Native C++

Writing Unit Tests in Visual Studio for Native C++

  • Comments 30

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


image

Setting up the projects

As I mentioned above, the first step is to setup the projects correctly:

  1. Create a new Win32 Project and select Static library in the Application Settings screen. This is where you’ll add your production code
  2. Create a second Win32 Project for your production EXE or DLL
  3. Create a Test Project (in the C++ section of the Add New Project dialog box)
  4. 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:

  1. Right click the test project and select Properties
  2. In the Configuration combo box, select All Configurations to ensure the changes below are made to both Debug and Release configurations
  3. Click the Common Properties node and then Framework and References
  4. Click Add New Reference…, select the static library and click OK
  5. Click the Configuration Properties node, then click the C/C++ node
  6. Click in the Additional Include Directories property and type something like this (substitute the name of your library project for ProductLibrary):
    $(SolutionDir)\ProductLibrary;%(AdditionalIncludeDirectories)
  7. 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:

[TestMethod]
void ShouldReturn1()
{
std::unique_ptr<SomeClass> pClass(new SomeClass());
Assert::AreEqual<int>(1, pClass->SomeValue());
};

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.

Update

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++.

Final Thoughts

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.

  • FYI, to use IntelliSense with managed code all you need to do is to toggle the Common Language Runtime Support switch of the C++ property pages:

    - when writing: select "No Common Language Runtime support"

    - when compiling: use "Common Language Runtime support (/clr)"

    Some IntelliSense is better than no IntelliSense. :)

    Maybe someone could make an addin or a macro to toggle the switch based on compilation.

  • Thanks for the suggestion, Michael. I'm going to play around with this and see if I can figure out how to make this automatic since that would be a really nice experience. Having IntelliSense for the code that I'm testing, but not of .NET, would be a very nice improvement.

  • John, thanks for the writeup.  It got me up and running very quickly with TDD, C++ and VS 2010.

    I'm also targeting 64 bit platforms, so I noticed that the pointer tests should be using _PTR types, such as

    Assert::AreNotEqual<DWORD_PTR>(0, (DWORD_PTR) pClass, "Should not be null");

    Thanks again.

  • John, Thanks a lot.

    Seems like a good idea. I'll be moving from CppUnit to VSUTF very soon.

  • I thanked you a little too early there.

    Write unit tests with no intellisense support? Are you kidding me? I'd rather use notepad.

  • Ardfry, thanks for the catch on DWORD_PTR. I haven't done 64-bit programming yet, so I didn't know about using _PTR for pointers. I've fixed the blog post.

    Elroy, yes, that is a pain. I mentioned the lack of IntelliSense in my post under Tips and Tricks. Also, there is a comment from Michael (the first comment) about how to get IntelliSense for the native C++ calls to work within the unit tests. But right now it's manual. I have some ideas on how to make it automatic, but I haven't had time to try it.

  • Hi John,

    Thanks for the writeup and the heads-up about the "quirky" features.

    Have to say, no intellisense is a killer.  I was looking forward to using the built-in functionality of VS2010 to do TDD, but this half-arsed support is rubbish.  It even fires a "not implemented" exception when creating a project for the first time.  Not particularly confidence inspiring :).    http://twitpic.com/3aajf8

    All the best,

    Ash

  • I did some research over the weekend and found various posts talking about a product called Visual Assist X that is supposed to have support IntelliSense in managed C++. I just installed a trial copy and I can report that it does, in fact, provide full IntelliSense when writing unit tests in managed C++. With this add-in, I now have the best of both worlds!

  • Actually, you cannot use MSTest and C++/CLI to test native x64 code at all.  In order to call native x64 code from C++/CLI, you need to build using the x64 platform.  The test project will build fine, but MSTest cannot load x64 assemblies.  This very unfortunate limitation of MSTest means that you can only test Win32 native code using MSTest and C++/CLI.  To test native x64 code with MSTest, you would need to write your tests in C# and call native functions with P/Invoke, and compile your C# test project with the Any CPU platform.  See this blog post for more info:

    blogs.msdn.com/.../visual-studio-team-test-load-agent-goes-64-bit.aspx

  • Adam, thanks for pointing that out. We don't have a requirement to deliver a 64-bit version of our application, so I hadn't run into hte x64 limitation you described. Do you find many cases where 64-bit code doesn't behave properly while the 32-bit code does?

  • Hi John, ideally one would write code in such a way that the same source code compiles and executes correctly on 32 bit and 64 bit platforms.  However in some cases this isn't possible, and even when it is, code doesn't end up being portable between 64-bit and 32-bit platforms automatically.  So there is always the possibility of errors on one platform or another.  I have never seen a case where a large body of code that had only been tested on 32-bit platforms was converted to 64-bit by just recompiling and having everything work correctly.  So I would not consider it an option to ship 64-bit code that had only been tested in 32-bit mode.   I think whatever test technology you choose must support testing on all of the platforms you plan to support, so I think the MSTest with C++/CLI approach unfortunately isn't useable by people who need to ship 64-bit code.

  • native c++ unit testing:

    www.boost.org/doc/libs/1_45_0/libs/test/doc/html/index.html

  • till IntelliSense become available on C++/CLI, I chose WinUnit@CodePlex

  • It's incredible that there are people who don't know Visual Assist. I've been using it since VC6!! VS intellisense is/was/will be(?) nothing compared with VA.

  • You can found here codebasequality.com a useful technique to avoid limitations due to use of c++\CLI.

Page 1 of 2 (30 items) 12
Leave a Comment
  • Please add 3 and 8 and type the answer here:
  • Post