Unit and Regression Testing by Adrian McCarthy Example 1: (a) TVECTOR.OBJ : TVECTOR.CPP VECTOR.H TVECTOR.EXE : TVECTOR.OBJ VECTOR.OBJ (b) TVECTOR.OUT : TVECTOR.EXE TVECTOR.IN TVECTOR.EXE < TVECTOR.IN > TVECTOR.OUT (c) TESTOUTS = TVECTOR.OUT regress : $(TESTOUTS) Example 2: accept: del *.ref copy *.out *.ref regress: $(TESTOUTS) echo "Regression Report" > REGRESS.RPT fc TVECTOR.REF TVECTOR.OUT >> REGRESS.RPT # ... one for each unit test Listing One ###################################################################### # Makefile with automated regression testing # Copyright 1996 Adrian C. McCarthy ###################################################################### # Written for Borland MAKE. This copy has been simplified to demonstrate the # integration of unit testing and automated regression testing. It # is not a complete platform for building a program. ###################################################################### # Targets # accept copies test results to reference directory # all builds all executables (default) # clean deletes all non-source files (executables, objs, etc.) # regress builds all unit test and creates a regression report # release PKZIPs the deliverable files and puts them in BINDIR # Option Macros # DEBUG if defined, compile and link with debugging information # MODEL memory model to compile for # (l, m, c, or s for large, medium, compact, or small) # PLATFORM 2, 3, 4, or 5 for 286, 386, 486, or Pentium # Directory Macros # BINDIR directory to place executables # TINDIR directory containing module test data # OBJDIR directory to place object files and libraries # OUTDIR directory to place test results # REFDIR directory where reference test results are kept # SRCDIR directory containing sources ###################################################################### # Target platform !ifdef PLATFORM PLATFORM=-$(PLATFORM) !endif # Debug control !ifdef DEBUG CDBGOPT = -v -DDEBUG -w # -v include debug info # -DDEBUG #defines DEBUG # -w display all warnings LDBGOPT = /v !message Debugging enabled -- no optimization !else CDBGOPT = -Ox $(PLATFORM) !message Optimization enabled -- no debugging !endif !ifndef BINDIR BINDIR = ..\bin !endif !ifndef TINDIR TINDIR = ..\test\in !endif !ifndef OBJDIR OBJDIR = ..\obj !endif !ifndef OUTDIR OUTDIR = ..\test\out !endif !ifndef REFDIR REFDIR = ..\test\ref !endif !ifndef SRCDIR SRCDIR = ..\source !endif # Memory model !ifndef MODEL MODEL = s !message Using small memory model by default. !endif ####### Tools and Options ####### CMDLOPT = -m$(MODEL) -I$(INCLUDE) STARTUP = c0$(MODEL).obj RUNTIME = emu.lib math$(MODEL).lib c$(MODEL).lib # Borland C/C++ compiler CC = bcc -c COPTS = $(CDBGOPT) $(CMDLOPT) # Borland TLINK LINK = tlink LOPTS = /c /m /n /Tde /L$(LIB);$(OBJDIR) $(LDBGOPT) # File Compare -- Use your favorite DIFF program here! DIFF = fc ####### Paths ####### .path.exe=$(BINDIR) .path.map=$(OBJDIR) .path.lib=$(OBJDIR) .path.obj=$(OBJDIR) .path.c =$(SRCDIR) .path.cpp=$(SRCDIR) .path.in =$(TINDIR) .path.out=$(OUTDIR) .path.rpt=$(OUTDIR) ####### Files ####### # Note, for each new unit test, you need to add a $(DIFF) line to the # regress.rpt target. You also need to add an explicit link rule for # the corresponding unit test. This is because the $** and $? macros # don't work in implicit rules nor do they work with macro modifiers. TESTEXES = tvector4.exe tmatrix4.exe tprepro.exe TESTOUTS = tvector4.out tmatrix4.out tprepro.out REGRPT = $(OUTDIR)\regress.rpt .precious $(TESTOUTS) $(REGRPT) ####### Rules ####### .c.obj: $(CC) $(COPTS) -n$(OBJDIR) $< .cpp.obj: $(CC) $(COPTS) -n$(OBJDIR) $< # This rule runs a unit test. .in.out: $(BINDIR)\$&.exe < $< > $@ ####### Pseudo-targets ####### all : $(EXES) $(REGRPT) # The accept target copies the current test results to the reference # directory. To avoid costly accidents, we generally keep the # reference copies read-only, and we make backups of the current ones # right before replacing them. accept : $(TESTOUTS) -attrib -r $(REFDIR)\*.bak -del $(REFDIR)\*.bak rename $(REFDIR)\*.out *.bak copy $(OUTDIR)\*.out $(REFDIR) attrib +r $(REFDIR)\*.out clean : -del $(OBJDIR)\*.obj -del $(BINDIR)\*.exe -del $(BINDIR)\*.map -del $(OUTDIR)\*.out -del $(REGRPT) regress : $(REGRPT) release : $(EXES) $(ZIP) $(BINDIR)\ART96.ZIP $(EXES) ####### Regression testing ####### # The macro modifiers don't work on the $** or $? targets, so # you have to add a $(DIFF) line for each new unit test. $(REGRPT) : $(TESTOUTS) echo Regression Test Report > $(REGRPT) echo ====================== >> $(REGRPT) $(DIFF) $(REFDIR)\tvector4.out $(OUTDIR)\tvector4.out >> $(REGRPT) $(DIFF) $(REFDIR)\tmatrix4.out $(OUTDIR)\tmatrix4.out >> $(REGRPT) $(DIFF) $(REFDIR)\tprepro.out $(OUTDIR)\tprepro.out >> $(REGRPT) ####### Dependencies ####### vector4.obj : vector4.cpp vector4.h tvector4.obj : tvector4.cpp xbase.h vector4.h tvector4.exe : tvector4.obj vector4.obj $(LINK) $(LOPTS) @&&| $(STARTUP)+ $** $*.exe $*.map $(RUNTIME) | tvector4.out : tvector4.exe tvector4.in matrix44.obj : matrix44.cpp matrix44.h tmatrix4.obj : tmatrix4.cpp xbase.h matrix44.h tmatrix4.exe : tmatrix4.obj matrix44.obj vector4.obj $(LINK) $(LOPTS) @&&| $(STARTUP)+ $** $*.exe $*.map $(RUNTIME) | tmatrix4.out : tmatrix4.exe tmatrix4.in prepro.obj : prepro.cpp prepro.h tprepro.obj : tprepro.cpp prepro.h tprepro.exe : tprepro.obj prepro.obj $(LINK) $(LOPTS) @&&| $(STARTUP)+ $** $*.exe $*.map $(RUNTIME) | tprepro.out : tprepro.exe tprepro.in Listing Two // Unit test for Vector4.cpp. Reads a file of n vectors, exercises the Vector4 // functions on those vectors, and outputs the results. The input file should // start with an integer (the number of vectors) followed by the vectors. #include #include "vector4.h" #define TF(x) (x ? "true " : "false") main() { int count, i, j; cin >> count; // number of vectors to read from cin Vector4 *pV = new Vector4[count]; Vector4 temp; cout << "VECTOR4 Regression Test" << endl << "=======================" << endl << endl; cout << "Reading " << count << " vectors." << endl; // The Vector4 module supports iostream input and output which // must be tested, but is also very convenient for this unit test // in general. cout << endl << "I/O Test" << endl << "========" << endl; for (i = 0; i < count; i++) { cin >> pV[i]; cout << "I/O " << i << " " << pV[i] << endl; } cout << endl << "Unary Functions" << endl << "===============" << endl; for (i = 0; i < count; i++) { cout << pV[i] << " Homogeneous: " << TF(pV[i].IsHomogeneous()) << endl; cout << "Homogenize " << pV[i] << " == "; pV[i].Homogenize(); cout << pV[i] << endl; cout << pV[i] << " Unit: " << TF(pV[i].IsUnit()) << endl; temp = pV[i]; if (temp.Magnitude() != 0.0) { temp.Unitize(); cout << "Unitize " << pV[i] << " == " << temp << " (" << temp.Magnitude() << ")" << endl; } else { cout << "Can't make zero vector unit length." << endl; } cout << "||" << pV[i] << "|| = " << pV[i].Magnitude() << endl; cout << "||" << pV[i] << "||^2 = " << pV[i].MagSquare() << endl; temp = -pV[i]; cout << " -" << pV[i] << " = " << temp << endl; temp = 0*pV[i]; cout << "0*" << pV[i] << " = " << temp << endl; temp = 1.0*pV[i]; cout << "1*" << pV[i] << " = " << temp << endl; temp = pV[i]*2; cout << pV[i] << "*2" " = " << temp << endl; cout << "---" << endl; } cout << endl << "Binary functions" << endl << "================" << endl; for (i = 0; i < count; i++) { for (j = 0; j < count; j++) { cout << pV[i] << " == " << pV[j] << ": " << TF(pV[i] == pV[j]) << endl; cout << pV[i] << " != " << pV[j] << ": " << TF(pV[i] != pV[j]) << endl; temp = pV[i] + pV[j]; cout << pV[i] << " + " << pV[j] << " = " << temp << endl; temp = pV[i] - pV[j]; cout << pV[i] << " - " << pV[j] << " = " << temp << endl; temp = pV[i]; temp += pV[j]; cout << pV[i] << " += " << pV[j] << " = " << temp << endl; temp = pV[i]; temp -= pV[j]; cout << pV[i] << " -= " << pV[j] << " = " << temp << endl; cout << pV[i] << " dot " << pV[j] << " = " << Dot(pV[i], pV[j]) << endl; temp = Cross(pV[i], pV[j]); cout << pV[i] << " cross " << pV[j] << " = " << temp << endl; cout << "---" << endl; } } delete pV; return 0; } Listing Three 9 [1 0 0] [0 1 0] [0 0 1] [1 1 1] [0 0 0] [4 4 4 (2)] [1 2 3 (1)] [3 2 1 (0)] [1.234567 1.23456789 1.2345678901234567890]