Visual Studio 2008 offers the ability to write unit tests in
managed c++ code. In my current project work I have to use Visual
Studio 2005 because of external constraints.
In the following article I will describe
how…
- to setup and write unit tests in c++/cli for MbUnti
- to write the production code in unmanaged c++ with MFC
- to setup a test project for debugging
Prerequisites
You need to have Visual Studio 2005 and MbUnit installed.
Create a new Unit Test Project
1.) Create a new CLR Class Library.
2.) Add a reference to the MbUnit framework you want to use.
3.) Add the namespace MbUnit::Framework.
using namespace MbUnit::Framework;
4.) Add the [TestFixture] attribute to the public ref class you
want to use as test container.
[TestFixture]
public ref class HelloUnitTest
5.) Add a new public method to the test class and mark it with
the[Test] attribute.
public:
[Test]
void TestHello()
{
6.) Now the code should look like this:
// MyCppTest.h
#pragma once
namespace MyCppTest {
using namespace System;
using namespace MbUnit::Framework;
[TestFixture]
public ref class HelloUnitTest
{
public:
[Test]
void TestHello()
{
}
};
}
7.) Build the project and give it a first run in the MbUnit Gui
Runner.
Therefore click on Assemblies->Add Assemblies.. and select
the test dll.
You can expand the tree and you will see your test fixture and
your test method.
Now we have the first working version of the unit test. It
passes and we’re done! Done? Not quite. Now we
need to test the code we are writing to get paid for. Following the
rules of TDD we
need to test first.
So let’s look into the requirements: The module
should return a string that contains
“Hello†on request. (Ok, maybe this
is not a for a real word product, but you never
know…)
8.) Implementing the testing code
I know that the module is used in a MFC environment. So I first
need to enable support for that. Switch on 'Use MFC in a Shared
DLLâ€
Include afxwin.h in StdAfx.h
#include "afxwin.h"
Now write the the test code:
public:
[Test]
void TestHello()
{
HelloWorldProvider provider;
CString response = provider.request();
Assert::Contains("Hello", response);
}
first test failure is that we cannot compile, so we need to create
the production library.
Implementing the production code
The production library shall be unmanaged. So we go to the
solution, click ‘Add new
project’ and create a new MFC DLL.
Use the standard settings and finish the wizard (If you look
into the generated StdAfx.h file we’ll see the
what additionally might be added to the StdAfx.h file for the unit
test).
Add a new class HelloWorldProvider:
Implement the request function:
class HelloWorldProvider
{
public:
HelloWorldProvider(void);
~HelloWorldProvider(void);
CString request() const
{
return L"hello world!";
}
};
Compile the production code and compile the test code. The test
code does not compile I changed it to:
// MyCppTest.h
#pragma once
#include "..\HelloWorldProvider\HelloWorldProviderImpl.h"
namespace MyCppTest {
using namespace System;
using namespace MbUnit::Framework;
[TestFixture]
public ref class HelloUnitTest
{
public:
[Test]
void TestHello()
{
HelloWorldProvider provider;
CString response = provider.request();
Assert::Contains("Hello", gcnew String(response));
}
};
}
Compile again and I get a linker error:
1>------ Build started: Project: MyCppTest,
Configuration: Debug Win32 ------ 1>Compiling...
1>MyCppTest.cpp 1>Linking... 1>MyCppTest.obj : error
LNK2028: unresolved token (0A000010) "public: __thiscall
HelloWorldProvider::~HelloWorldProvider(void)"
(??1HelloWorldProvider@@$$FQAE@XZ) referenced in function "public:
void __clrcall MyCppTest::HelloUnitTest::TestHello(void)"
(?TestHello@HelloUnitTest@MyCppTest@@$$FQ$AAMXXZ)
1>MyCppTest.obj : error LNK2028: unresolved token (0A000011)
"public: __thiscall HelloWorldProvider::HelloWorldProvider(void)"
(??0HelloWorldProvider@@$$FQAE@XZ) referenced in function "public:
void __clrcall MyCppTest::HelloUnitTest::TestHello(void)"
(?TestHello@HelloUnitTest@MyCppTest@@$$FQ$AAMXXZ)
1>MyCppTest.obj : error LNK2019: unresolved external symbol
"public: __thiscall HelloWorldProvider::~HelloWorldProvider(void)"
(??1HelloWorldProvider@@$$FQAE@XZ) referenced in function "public:
void __clrcall MyCppTest::HelloUnitTest::TestHello(void)"
(?TestHello@HelloUnitTest@MyCppTest@@$$FQ$AAMXXZ)
1>MyCppTest.obj : error LNK2019: unresolved external symbol
"public: __thiscall HelloWorldProvider::HelloWorldProvider(void)"
(??0HelloWorldProvider@@$$FQAE@XZ) referenced in function "public:
void __clrcall MyCppTest::HelloUnitTest::TestHello(void)"
(?TestHello@HelloUnitTest@MyCppTest@@$$FQ$AAMXXZ)
1>c:\temp\MyCppTest\Debug\MyCppTest.dll : fatal error LNK1120: 4
unresolved externals 1>Build log was saved at
"file://c:\temp\MyCppTest\MyCppTest\Debug\BuildLog.htm"
1>MyCppTest - 5 error(s), 0 warning(s) ========== Build: 0
succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
Now we have the following choices: 1.) If the class is exposed in
the dll interface, we can statically link to the production code
which is the most accurate solution because you are testing the
binary code that get’s shipped. 2.) If the class
is not part of the dll interface, we can create a link library with
that class and link it to both: the production module and the test
module 3.) If the class is not part of the dll interface, we can
add the cpp file to the test project that a object file
get’s created for it and mark it as unmanaged .
I will now follow the 3rd approach…
Use ‘Add existing
…’ to add the
HelloWorldProviderImpl.cpp to the project, compile, run the test
and …
It fails! It complains that the returned string does not contain
“Helloâ€. The inspected string is
“hello world†which does not comply
to our requirements. So let’s correct that and
run again: It fails again! A closer look at the MbUnit message
reveals the problem:
String “Hello†does not contain
“hello worldâ€
The assert must be vice versa. Obviously this is a code comment bug
in MbUnit:
So let’s correct the order:
Assert::Contains(gcnew String(response),"Hello";
…and the test is GREEN:
Setting up for debugging
To debug into the test code simply set up the GUI runner as startup
project with the following options:
Now you can start the runner by pressing F5…
Finally some useful links
Executing
MbUnit GUI and Console from Visual Studio
Unit Testing with MBUnit (An Introduction) typeof
Goes to T::typeid for use of [ExpectedException =
ArgumentException::typeid] attribute
ps.: sorry for the poor code formatting. I was using first time
Code
Snippet plugin for Windows Live Writer.