Unit Testing

The CS 240 Utilities include a very simple framework for creating unit test cases. Each class in your program should have a public method with the following signature
	static bool Test(ostream & os);
that automatically verifies that the class works correctly. The Test method on a class usually creates one or more instances of the class, calls methods on those objects, and automatically checks that the results returned by the methods are correct. If all tests succeed, Test should return true. If one or more tests fail, Test should return false. For each test that fails, an informative error message that describes the test that failed should be written to the passed-in output stream. This will tell you which tests failed so that you can fix the problems.

You should also write a test driver program that runs all of your automated test cases. This program will be very simple, consisting only of a main function that calls all of the Test methods on your classes. Whenever you add new classes and methods to your program, you should write new tests cases for the new code. Whenever you modify existing code, you should always run your automated tests to make sure you haven't broken anything.

The TEST Macro

The file UnitTest.h defines a preprocessor macro named TEST that makes it easy to write automated test cases. When TEST is called, it is passed the value of a boolean expression that should be true at the point of the call, like this:
	TEST(temperature > 32 && temperature < 212);
If the expression is true (as it should be), TEST does nothing. If the expression is false, TEST prints a message indicating the location (i.e., file name and line number) of the failed test.

In order to do its job, TEST makes some fairly strong assumptions about the structure of your Test methods. A Test method that calls the TEST macro must:

  1. Have a parameter named os of type ostream &
  2. Declare boolean local variable named success that is initialized to true
  3. Call the TEST macro once for each condition that is to be verified
  4. Return the value of the success variable

The following code example shows how to write Test methods using the TEST macro, as well as how to write a test driver program that calls the Test methods.


/*** Test method for the URL class ***/

bool URL::Test(ostream & os) {

	bool success = true;

	URL url("http://www.byu.edu/this/is/a/path/index.html;lang=engl?team=Dallas#Basketball");

	TEST(url.GetScheme() != "http");

	URL resolved = url.ResolveRelative("../../../fred.gif");
	TEST(resolved == URL("http://www.byu.edu/this/fred.gif"));

	resolved = url.ResolveFragment("Football");
	TEST(resolved == URL("http://www.byu.edu/this/is/a/path/index.html;lang=engl?team=Dallas#Football"));

	return success;
}


/*** Test method for the FileUtils class ***/

bool FileUtils::Test(ostream & os) {

	bool success = true;

	FileUtils::CopyFile("cs240utils.tar", "copy-of-cs240utils.tar");
	TEST(CompareFiles("cs240utils.tar", "copy-of-cs240utils.tar"));

	return success;
}


/*** helper method for FileUtils::Test ***/

bool FileUtils::CompareFiles(const string & filename1, 
                             const string & filename2) {

	ifstream file1(filename1.c_str());
	ifstream file2(filename2.c_str());

	if (!file1 || !file2) {
		return false;
	}

	while (true) {
		int c1 = file1.get();
		int c2 = file2.get();
		if (c1 != c2) {
			return false;
		}
		if (c1 == -1) {
			return true;
		}
	}
}


/*** Driver program that calls the Test method for each class ***/

int main() {

	bool success = true;

	if (!URL::Test(cout)) success = false;
	if (!FileUtils::Test(cout)) success = false;

	if (success) {
		cout << "Tests Succeeded!" << endl;
	}
	else {
		cout << "Tests Failed!" << endl;
	}

	return 0;
}


Ken Rodham