Thursday, August 6, 2009

My Approach for Unit testing c++ with Visual Studio 2005

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.

image

2.) Add a reference to the MbUnit framework you want to use.

image

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.

image

You can expand the tree and you will see your test fixture and your test method.

image

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â€

image

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.

image

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:

image

image

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 …

image

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:

image

So let’s correct the order:
Assert::Contains(gcnew String(response),"Hello";
…and the test is GREEN:

image

Setting up for debugging

To debug into the test code simply set up the GUI runner as startup project with the following options:

image

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.

No comments:

Post a Comment