Replacing MSTest With Phil Nash's Catch Framework for Managed C++ Tests
Visual Studio 2010 has an option for creating C++ unit tests, but these tests are 'managed' C++. There are a number of problems with such tests:
The tests are slow to compile and slow to load/run.
Tests tend to be more complicated than needed - the Managed C++ compiler does not allow C++ value types as member variables for example, so these have to be kept as pointers and new/delete used.
The debugger doesn't understand C++ types very well, so when in managed code it is often not possible to view object values. Whilst it could be argued that using TDD should avoid most uses of the debugger, sometimes it's inevitable.
Sometimes the Managed C++ debugger gets a 'mind of its own' and decides to run a test to completion part way through debugging. This can be frustrating!
In the absence of Native C++ unit tests (note: VS2012 has these - see later) I wanted to be able to share the code base between MSTest managed tests and native tests so that the build system could happily run MSTest on its managed build and I could run native tests using Catch. This article describes my experiment to do this…
Differences in test philosophy
To persuade MSTest to look like Catch, we have to think about how the test needs to run. MSTest requires a class, but Catch does not. Catch allows multiple runs by using SECTIONs, but this doesn't figure in MSTest. So my first attempt used a TEST_CASE as a 'class' wrapper and a SECTION for each test method. This worked pretty well, except that it wasn't possible to specify individual tests to run (SECTIONs don't support tags).
Looking at the tests that I wanted convert, they tended to follow a pattern:
namespaceManagedTestProject{[TestClass]publicrefclassClassNameForTest{// some data that needs to be initialized...Stuff*m_data;//Use TestInitialize to run code before running each test[TestInitialize()]voidMyTestInitialize(){m_data=newStuff(...);}//Use TestCleanup to run code after each test has run[TestCleanup()]voidMyTestCleanup(){deletem_data;m_data=NULL;}public:[TestMethod]voidMethod1(){}[TestMethod]voidMethod2(){}};}
A specific solution
Fortunately, they always did this; declare pointer member variables for objects the test needed, create in TestInitialize and destroy in TestCleanup, in that order, followed by test methods. I realised that I could declare the data at namespace scope and wrap the initialisation/destruction in a helper class, something like this:
TEST_CASE_METHOD is a Catch macro that creates a class instance derived from setup_wrapper for each method, then runs the test in its own method. Unfortunately, not all my tests had TestInitiaize/TestCleanup methods, so I wanted to be able to specify those in a similar way to Native C++ tests. I tried several ways to do this, but ended up with a couple of macros, used like this:
If there isn't a setup method, there won't be a setup(int) or a cleanup(int) but we can fix that by defining a templated setup/cleanup function at namespace scope:
123456789101112131415161718192021222324
namespaceX{template<typenameT>voidsetup(Tt){}template<typenameT>voidcleanup(Tt){}...// no TEST_METHOD_INITIALIZE/CLEANUPstructsetup_wrapper{setup_wrapper(){// calls template functionsetup(0);}virtual~setup_wrapper(){// calls template functioncleanup(0);}};TEST_CASE_METHOD(setup_wrapper,"Method1");}
C++ overload resolution will always prefer the non-templated `setup(int)` if it exists, so will call the non-templated method.
# A final solution #
Now I can embed all the nasty bits in macros and mangle some names to avoid duplicate definitions so that my final shared codebase looks like this:
In VS2010, you can create a C++ test project like this:
This is always a managed C++ project and the macros above work fine. In VS2012, there are two options….but I think I'll leave that for another post ;-)