Thursday, April 26, 2012

Unit Testing private classes using C++/CX (Metro Apps)

Sometimes, we need testing some internal classes in a C++ project. Because of that, MS has proposed this way of testing internal classes:

http://msdn.microsoft.com/en-us/library/hh419385%28v=vs.110%29.aspx#sameProject


I was trying to put this method in practice but it doesn't work. I have some conversation in the MS social forums and the bad new is: This technique only work for native application :(


But I really need to test internal classes!!


So, I started working on this technique, that is very straight forward:


Basically, his technique consists in developping the Test Cases inside the project, but trigger them from outside.


Solution
|
|->TargetProject
|   |
|    --> MyTestSuite.cpp    <--- This file contains the real unit tests 
|                                (it has access to internal classes)
|
|->MetroUnitTestProject      <--- This is the unit test project to be run.
   |
    --> MyTestSuiteForward.cpp   <--- This file is the entry point, and  
                                               forward each unit test to the real 
                                               one defined in MyTestSuite.cpp
   

The steps to test internal classes are:


Step 1) Add the followings settings in the TargetProject


Include Directories
$(VCInstallDir)UnitTest\include;$(IncludePath)
Library Directories
$(VCInstallDir)UnitTest\lib;$(LibraryPath)



Step 2) Add a unit test in the TargetProject we named this file MyTestSuite.cpp.

Step 3) In MyTestSuite.cpp, add the following macros:

//---- Cut here ------------------
#include <Objbase.h>
public ref struct MTAEnabler
{
MTAEnabler()
{
CoUninitialize();
HRESULT result = CoInitializeEx(NULL, COINIT_MULTITHREADED);
}
};
#undef TEST_CLASS
#undef TEST_METHOD
#define TEST_CLASS(x) public ref class x: public ::MTAEnabler
#define TEST_METHOD(x) static void x()

//---------------------------------------

Step 4) Create a Metro C++ Unit Test project (MetroUnitTestProject), and add the TargetProject as reference in the project properties.

Step 5) In the TestSuite file (we named this file: MyTestSuiteForward.cpp), add the following macro:

#define FORWARD_TEST(ns, x) TEST_METHOD(x) { ns::x();}
#define SKIP_TEST(ns, x)  

Step 6) Develop a unit test in MyTestSuite.cpp. For Example:

  TEST_METHOD( TestMethod1)
{
Logger::WriteMessage(L"This unit test has access to internal classes");
Assert::AreEqual(1,1);
}

Step 7) Make the MetroUnitTestProject aware of this new Unit Test:

namespace UnitTestLibrary
{
    TEST_CLASS(  MyTestSuiteForward )
    {
    public:
        FORWARD_TEST( TargetProject ::UnitTest, TestMethod1)
    };
}

Step 8) Build the solution, and the new Unit Test will become available in the Unit Test Explorer windows.


Step 9) Now you can run this test.


Improvement: As you should note, we are adding a new class (MTAEnabler) and we are making the unit test a chlild of this new class. This to run the test in MTA mode, this enable us to wait from task finalization, or to use events to synchronize asynch tasks. This is very useful because WE ARE IN A UNIT TEST, NOT IN AN UI so the STA thread model is not a good model for Unit Test purpose.


This hack use COM api because the documented way to change the Thread model is not working so far :(



Documented way:

[Threading(ThreadingModel::MTA)]
or
[MTAThread]

No comments:

Post a Comment