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 <iostream.h>
#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]




