Visual Studio tries to make it easy to add unit testing, but there
is still a fair amount of work and a lot of customizations that needs
to be done.
I am working entirely with C++ compiled to native machine code. The
steps for a managed project are slightly different. The biggest
difference is that native code must be linked into the UnitTest
framework, either through DLLIMPORT or as statically linked libraries.
This means as much of the target project as possible should be
contained in libraries or DLL's. The executable project should contain
a single module to contain main() or WinMain() and immediately call
library functions for everything else. This will allow the UnitTest
framework to exercise as much of the target code as possible.
I am starting with an existing text manipulation library, IString,
and adding a UnitTest project to it. The IString project produces a
static-link library that will be the target. IString is currently
part of another solution, I want to extract it into its own solution
that will be shared with other projects so I will be creating a new
solution from scratch.
Open Visual Studio with no solution, a blank slate.
File > New > Project
Create the target project first: Templates > Visual C++ > Windows > Win32.
The solution name will be "ChipLib" and the project will be "IString".
Import the existing IString source code by manually copying the
files into the IString directory. Then add them to the project by
right-clicking the "Source Files" folder and "Add existing item(s)..."
Now that the project contains some C++ files I can set the
compiler properties for the project. 99% of the properties should be
identical for all platforms and configurations, keep the differences
to a minimum. The IString project makes extensive use of C++
templates to create Unicode and Ansi versions of the interfaces. This
requires most of the C++ source files to be included into a single
master file (nString.cpp). The included files need to be removed from
the build (but not the project), which is why they are flagged with
the red dots.
Copy the necessary files from the Include directory and add them to
the IString project. I added a "Build" folder to hold files related to
the build process.
I should now be able to build the IString library.
Create a new unit test project: File > Add > New Project...
Select Installed > Visual C++ > Test > Native Unit Test Project
Set the name to "UnitTest".
Update the UnitTest project properties to match the target. There
are some differences:
Set the output directory to "Test" since the unit test code is generally not
distributed.
"Additional Include Directories" should have "$(VCInstallDir)UnitTest\include"
The UnitTest project needs to link with the "Multi-threaded Debug (/MTd)" runtime
library
The UnitTest uses precompiled headers
Rename "unittest1.cpp" to "tIString.cpp". This will contain the unit
tests for the IString library. I prefix all the test code files with "t"
to avoid name collisions with files in the target.
As a personal preference, I like to "clean up" the wizard code. I
especially dislike embedding large functions inside the class declarations.
Both the library and UnitTest projects should now build. I can run
the unit tests, but they don't do anything yet.
TestUtil
The unit test code has two goals: first to test the target code as
completely as possible, then if a test fails communicate enough
information so I can easily identify and fix the problem. I need to
add some support code to make it easy to create verbose diagnostic
reports, without worrying about allocating buffers.
I created simplified versions of the IString and TmpBuf classes.
TmpBuf allocates memory from a static buffer on a round-robin basis.
The blocks returned by TmpBuf remain valid until the allocator wraps
around its entire internal heap. This has the advantage of allowing
the temporary buffer to remain valid for a time after its allocator
has fallen out of scope, and I can write code like this: Assert::Fail(tTXT("Bad pointer: %p",pMem));
An unnamed TXT object is created and destroyed before the Assert::Fail()
code prints the text, but the TmpBuf remains valid.
I put the TestUtil declarations in stdafx.h:
stdafx.h:
/*************************************************************************/
/** StdAfx.h: Precompiled header for ChipLib/UnitTest. **/
/** (C)2017 Chip Doran, nlited systems. **/
/*************************************************************************/
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
#pragma once
#include "targetver.h"
#include <Windows.h>
#include "Util.h"
// Headers for CppUnitTest
#include "CppUnitTest.h"
/*************************************************************************/
/** Simplified versions of TmpBuf and IString for internal UnitTest use.**/
/*************************************************************************/
class tTmpBuf {
public:
tTmpBuf(void);
~tTmpBuf(void);
bool Create(UINT szHeap);
void *Get(UINT ctBytes) { return((void*)Get2(ctBytes)); };
WCHAR *GetW(UINT ctChr) { return((WCHAR*)Get2((ctChr+1)*2)); };
void Free(void *pBuf);
private:
BYTE *Get2(UINT ctBytes);
BYTE *Get3(UINT ctBytes);
UINT szHeap;
UINT nNext;
BYTE *pHeap;
HANDLE mtxHeap;
};
extern tTmpBuf gTmpBuf;
class tTXT {
public:
tTXT(void);
tTXT(const char *Fmt, ...);
~tTXT(void);
const WCHAR *Text(void) { return(pBuf ? pBuf:L""); };
private:
const WCHAR *Append(const char *Src);
const WCHAR *PrintV(const char *Fmt, va_list &ArgList);
__inline bool Grow(UINT szNew) { return(szNew+1 < szBuf ? true:Grow2(szNew+100)); };
bool Grow2(UINT szNew);
WCHAR *pBuf;
UINT szBuf;
UINT ctChr;
};
//EOF: STDAFX.H
Being able to debug the unit test code is just as important as
debugging the code under test. Nothing is more annoying than spending
a lot of time chasing a bug that turns out to be in the test
itself.
For reasons I do not understand, launching the tests using Test >
Debug > All Tests will not halt at breakpoints in the test code.
Right-click one or more tests from the Test Explorer and select "Debug
selected tests" instead.
Comments are moderated.
Anonymous comments are not visible to other users until approved.
The content of comments remains the intellectual property of the poster.
Comments may be removed or reused (but not modified) by this site at
any time without notice.