Using CXXTest with VC6 and VC7 at the command line

This can be a pig to set up out of the box.  I did get it working, tho, and here's how.  I was using Eclipse as my editor.

Directory structure

    c:---> workspace----->cxxtest
                |
                --------->engine----->src
                            |
                            --------->tests
                            |
                            --------->testDict
                            |
                          <files>

The cxxtest directory is where cxxtest is unpacked.  It contains the supplied directories cxxtest, docs and sample.  This is all out of the box.

My code (.cpp, .h) is in the src directory. 

My unit tests are in .h files in the tests directory.

The testDict directory is not necessary.  It contains a VC6 console project, created using the VS6 IDE, which has a single .cpp file, and includes other files from the src directory.  The point of this is to ensure that I can debug code using the IDE when I want to.  It has no other connection to cxxtest.  However the instructions to compile it are also in the Makefile below.

The <files> are:

Makefile

The Makefile works with Microsofts nmake, cl and link commands.  Here it is:

#
#	To run:
#	Start|Run|cmd
#	cd \workspace\engine
#	vcvars32.bat (for 6.0 ) [or vsvars32.bat for 7 -- perf tests show code runs faster under 6]
#	nmake clean
#	nmake
#	nmake tests
#
#
#	Common problems:
#	1: If you get a link error with XLen in std lib, it's because you have VC6 includes ahead of VC7 ones in the include variable
#   2: error: runner.cpp
#      atlconv.h(48) : error C2065: '_ASSERTE' : undeclared identifier
#	   This one is cured by #include "windows.h" -- if you want to do that!
#
HOME_DIR=.\testDict
OBJ_DIR=.\obj
BIN_DIR=.\bin
SRC_DIR=.\src

ALL : testDict
	@echo erasing unwanted files
	-@erase "$(BIN_DIR)\testDict.ilk"
	-@erase "$(BIN_DIR)\testDict.pdb"
	-@erase /Q "tests\*op*.txt"
	@echo "Running code"
	"$(BIN_DIR)\testDict.exe"

CLEAN :
	-@erase /Q "tests\*tempfiles*.txt"
	-@erase "$(SRC_DIR)\runner.cpp"
	-@erase /Q "$(OBJ_DIR)\*.*"
	-@erase /Q "$(BIN_DIR)\*.exe"

#---------------------------------------------------------------------------------
#
#	* CXX_Test
#
#---------------------------------------------------------------------------------

CXXTEST_DIR=..\cxxtest
TEST_DIR=./tests
TESTS = $(TEST_DIR)/*.h
TESTGEN = perl -w $(CXXTEST_DIR)/cxxtestgen.pl

#---------------------------------------------------------------------------------
#
#	* Create temporary directories
#
#---------------------------------------------------------------------------------
!IF "$(OS)" == "Windows_NT"
NULL=
!ELSE
NULL=nul
!ENDIF

#
#  These targets are dependencies on the compile and link, just to check directories exist
#
"$(BIN_DIR)" :
    if not exist "$(BIN_DIR)/$(NULL)" mkdir "$(BIN_DIR)"

"$(OBJ_DIR)" :
    if not exist "$(OBJ_DIR)/$(NULL)" mkdir "$(OBJ_DIR)"

#---------------------------------------------------------------------------------
#
#	Link
#
#---------------------------------------------------------------------------------

LINK32=link.exe
LINK32_LIBS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib
LINK32_OPTS_DEBUG=/nologo /subsystem:console /incremental:yes /debug /machine:I386
#LINK32_OPTS_RELEASE= /nologo /subsystem:console /incremental:no /machine:I386
LINK32_OPTS_RELEASE= /INCREMENTAL:NO /NOLOGO /SUBSYSTEM:CONSOLE /MACHINE:X86

BINARY=/out:"$(BIN_DIR)\testDict.exe"
LINK32_OBJS= \
	"$(OBJ_DIR)\myfile1.obj" \
	"$(OBJ_DIR)\myfile2.obj" \
	"$(OBJ_DIR)\testDict.obj"

testDict : "$(BIN_DIR)" $(DEF_FILE) $(LINK32_OBJS)
    $(LINK32) $(BINARY) $(LINK32_LIBS) $(LINK32_OPTS_RELEASE) $(LINK32_OBJS)


#---------------------------------------------------------------------------------
#
#	Include file of other file dependencies for link step
#
#---------------------------------------------------------------------------------

!IF "$(NO_EXTERNAL_DEPS)" != "1"
!IF EXISTS("$(HOME_DIR)\testDict.dep")
!INCLUDE "$(HOME_DIR)\testDict.dep"
!ELSE
!MESSAGE Warning: cannot find "$(HOME_DIR)\testDict.dep"
!ENDIF
!ENDIF

#---------------------------------------------------------------------------------
#
#	Compile step (/c = no link)
#
#---------------------------------------------------------------------------------

CPP=cl.exe

MYINCLUDES=/I $(CXXTEST_DIR) /I $(SRC_DIR) /I $(BIN_DIR) /I $(HOME_DIR) /I $(TEST_DIR) /I "."
#MYDEBUGDEFINES= /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS"
#CPP_PROJ=/nologo /MLd /W3 /Gm /GX /ZI /Od $(MYINCLUDES) $(MYDEBUGDEFINES) /Fo"$(OBJ_DIR)\\" /Fd"$(OBJ_DIR)\\" /FD /GZ /c
MYRELEASEDEFINES= /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS"
CPP_PROJ=/EHsc /nologo /ML /W3 /GX /O2 $(MYINCLUDES) $(MYRELEASEDEFINES) /Fo"$(OBJ_DIR)\\" /Fd"$(OBJ_DIR)\\" /FD /c


#
#	Each step must have a target with the .obj name (referenced in the testDict dependencies above)
#	Each target is of the form : 
#
SOURCE=$(SRC_DIR)\myfile1.cpp
"$(OBJ_DIR)\myfile1.obj" : $(SOURCE) "$(OBJ_DIR)"
	$(CPP) $(CPP_PROJ) $(SOURCE)

SOURCE=$(SRC_DIR)\myfile2.cpp
"$(OBJ_DIR)\myfile2.obj" : $(SOURCE) "$(OBJ_DIR)"
	$(CPP) $(CPP_PROJ) $(SOURCE)

SOURCE=$(HOME_DIR)\testDict.cpp
"$(OBJ_DIR)\testDict.obj" : $(SOURCE) "$(OBJ_DIR)"
	$(CPP) $(CPP_PROJ) $(SOURCE)


#---------------------------------------------------------------------------------
#
#	Test Runner
#
#---------------------------------------------------------------------------------

RUNNER32_OBJS= \
	"$(OBJ_DIR)\runner.obj" \
	"$(OBJ_DIR)\myfile1.obj" \
	"$(OBJ_DIR)\myfile2.obj"

RUNNER_SOURCE=$(SRC_DIR)\runner.cpp

# Main tests target
tests: $(BIN_DIR)\runner.exe
	@echo "running tests"
	-@erase $(RUNNER_SOURCE)
	$(BIN_DIR)\runner.exe

"$(BIN_DIR)\runner.exe" : "$(BIN_DIR)" $(RUNNER32_OBJS) $(SRC_DIR)\runner.cpp
    $(LINK32) /out:"$(BIN_DIR)\runner.exe" $(LINK32_LIBS) $(LINK32_OPTS) $(RUNNER32_OBJS)

# Runner.cpp depends on anything in the tests dir changing
# Create runner.cpp
"$(OBJ_DIR)\runner.obj": $(RUNNER_SOURCE) "$(OBJ_DIR)" $(TESTS)
	$(CPP) $(CPP_PROJ) $(RUNNER_SOURCE)

CXXTESTGEN_FLAGS =        \
	--runner=ParenPrinter \
	--have-eh             \
	--abort-on-fail

"$(RUNNER_SOURCE)": $(TESTS)
	@echo "Creating runner.cpp"
	$(TESTGEN) $(CXXTESTGEN_FLAGS) -o $(RUNNER_SOURCE) --error-printer $(TESTS)

Running the tests

To run this:

  1. Open a console (Start|Run|Cmd)
  2. cd \workspace\engine
  3. set the environment variables.  Enter vcvars32 or vsvars32 depending on which you want to run (both will work, but VC7 seems slower) and hit enter.
  4. nmake clean  (to remove debris)
  5. nmake tests (to run the tests)
  6. nmake (to compile the test program testDict)

Sample tests

This file is some of the tests for some code of mine called timer_util.h and timer_util.cpp, which contains a class QLTimer.  This is a stopwatch class, which measures time.  It has two methods, StartTimer() and StopTimer(), and two others getTicksElapsed() and getSeconds() to tell you how long the time interval between the two.

#ifndef __TIMERUTILTEST_H
#define __TIMERUTILTEST_H

#include 
#include 		// for cout
#include      // for sleep()
#include "timer_util.h"
using namespace std;

class TimerUtilTest : public CxxTest::TestSuite
{
public:

   	QLTimer * myObj;

	void setUp( void )
	{
		myObj = new QLTimer();
	}

	void tearDown( void )
	{
		delete myObj;
	}

    void testTimer( void )
    {
        TS_ASSERT_EQUALS( myObj->getTickFrequency(), "Ticks are 0 3579545 per second" );
        TS_ASSERT_EQUALS( myObj->getTicksElapsed(), 0 );
        TS_ASSERT_EQUALS( myObj->getSeconds(), 0.0 );
        myObj->startTimer();

        for (int i=0;i<10000;i++){}

        myObj->stopTimer();
        TS_ASSERT( myObj->getTicksElapsed() < 200 );

    }

    void testTimer2( void )
    {
        myObj->startTimer();

        Sleep(1000);  // millisecs; sleep() on unix

        myObj->stopTimer();
        cerr << "Time in Seconds to wait 1000 millisec=" << myObj->getSeconds() << endl;
        TS_ASSERT( myObj->getSeconds() > 0.9 );

    }
};

#endif // __TIMERUTILTEST_H

I hope that is helpful.  Getting that makefile to work took two days, but it really isn't too hard once you have all the options for the compiler.

You can add extra include directories on to the end easily enough, if you have your .h's in other places.  If you have .cpp's in other places which you need to link in, create a MYSRC_DIR variable, and point it to that directory; then make sure the extra .cpp files are specified being in $(MYSRC_DIR) rather than in $(SRC_DIR) as mine are in the above example.

Constructive feedback is welcomed to Roger Pearse.

This page has been online since 30th November 2006.

Return to Roger Pearse's Pages