How to test MFC applications using Visual Assert or cfix
Automated testing of GUI applications is tricky. It is not only tricky because testing the GUI itself is hard (despite there being good tools around), it is also tricky because GUI applications often tend to be a bit hostile towards unit testing.
One class of GUI applications for which this kind of hostility often applies is MFC applications. Although MFC allows the use of DLLs, components, etc, the framework still encourages the use of relatively monolithic architectures.
Regardless of whether this is a good or bad thing, the question is how to make unit testing MFC applications feasible and less painful.
In one of the last releases, both cfix and Visual Assert introduced the abilify to embed unit tests into executable modules. That is, the framework allows unit tests to be compiled and linked into the main application .exe file. Such an executable can then be run in two modes: On the one hand, it can be launched regularly and the existance of unit tests will largely go unnnoticed. On the other hand, the executable can be launched indirectly via cfix of Visual Assert – and in this case, the application will not have its WinMain routine run, but rather have its unit tests be executed.
Although this feature enables us to do unit testing without having to rethink or tweak the application’s architecture more than necessary, the question still in how far MFC likes this kind of testing.
At this point, a distinction has to be made on whether the unit tests make use of MFC APIs or not. Tests that only exercise internal methods will run smoothless, without further setup or precautions needed. Of course, this also holds for tests that make of of basic MFC functionality such as using classes like CString or CArray.
For test that make use of more “interesting” MFC functions, however, it is quite possible that you will observe slightly strange behavior: API calls failing, fields not having the proper value, or maybe even crashes.
An example for this is the field AFX_MODULE_STATE::m_hCurrentInstanceHandle
. During a normal run, this field will hold the address of the current module. In a unit tests, however, the following assertion will fail:
CFIX_ASSERT( AfxGetModuleState()->m_hCurrentInstanceHandle != 0 );
If you think about this for a minute, this should not really come as a surprise. As described previously, Visual Assert/cfix, when attempting to run tests embedded into an executable module, will not call the applocation’s WinMain/main routine. They will ensure that all initializers (in particular: constructors of global C++ objects) are run, but calling the actual main routine is effectively skipped. Many of the MFC APIs, however, rely on quite a bit of initialization work that is performed during startup. Some of this work is conducted as part of constructors of global objects executed, a significant part of this work, however, is done in AfxWinInit
.
Quoting MSDN:
This function is called by the MFC-supplied WinMain function, as part of the CWinApp initialization of a GUI-based application, to initialize MFC.
And indeed, if you look at the default MFC WinMain routine, AfxWinMain
, you will see that it calls AfxWinInit
.
The key to using “interesting” MFC APIs in your unit tests therefore is to make sure that AfxWinMain
has been called before.
Unfortunately, there is no counterpart to AfxWinInit, so initializing MFC in a fixture’s Before or Setup routine and shutting MFC down in a After or Teardown routine is not feasible. Rather, you have to call it once per process, something that is a bit unusual for unit testing and requires a tiny bit of manual work. The easiest way to accomplish this is to a lazy initialization helper routine:
static void InitalizeMfc()
{
static BOOL Initialized = FALSE;
if ( ! Initialized )
{
CFIX_ASSERT( AfxWinInit(
::GetModuleHandle(NULL),
NULL,
::GetCommandLine(),
0 ) );
Initialized = TRUE;
}
}
…and call it from each fixture’s Setup routine:
static void SetUp()
{
InitalizeMfc();
}
With this in place, MFC APIs will now work properly and AfxGetModuleState()->m_hCurrentInstanceHandle
will contain a proper address.
With this bit of MFC background in mind, writing unit tests for MFC applications should be not much different than writing tests for any other kind of application.
One final tip: Adding unit tests to your project introduces a dependeny to cfix.dll. In your final build, you’ll want to remove (#ifdef out, for example) all your tests so this dependency will disappear. During development, however, this dependency might be a bit of a drag because there might be machines on which cfix is not available. To alleviate this situation, consider making cfix.dll a delay-load: Unless you want to run unit tests, it is then ok to miss cfix.dll on other machines.