Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Automated testing

So what does a unit test look like? Too often developers just use some well-behaved input to produce some expected output, which they inspect visually. Two dangers exist in this approach. First, programs don t always receive only well-behaved input. We all know that we should test the boundaries of program input, but it s hard to think about this when you re trying to just get things working. If you write the test for a function first before you start coding, you can wear your tester hat and ask yourself, What could possibly make this break? Code a test that will prove the function you ll write isn t broken, and then put on your developer hat and make it happen. You ll write better code than if you hadn t written the test first.

The second danger is that inspecting output visually is tedious and error prone. Most any such thing a human can do a computer can do, but without human error. It s better to formulate tests as collections of Boolean expressions and have a test program report any failures.

For example, suppose you need to build a Date class that has the following properties:

      A date can be initialized with a string (YYYYMMDD), three integers (Y, M, D), or nothing (giving today s date).

      A date object can yield its year, month, and day or a string of the form YYYYMMDD .

      All relational comparisons are available, as well as computing the duration between two dates (in years, months, and days).

      Dates to be compared need to be able to span an arbitrary number of centuries (for example, 1600 2200).

Your class can store three integers representing the year, month, and day. (Just be sure the year is at least 16 bits in size to satisfy the last bulleted item.) The interface for your Date class might look like this:

//: C02:Date1.h
// A first pass at Date.h.
#ifndef DATE1_H
#define DATE1_H
#include <string>
 
class Date {
public:
// A struct to hold elapsed time:
struct Duration {
int years;
int months;
int days;
Duration(int y, int m, int d)
: years(y), months(m), days(d) {}
};
Date();
Date(int year, int month, int day);
Date(const std::string&);
int getYear() const;
int getMonth() const;
int getDay() const;
std::string toString() const;
friend bool operator<(const Date&, const Date&);
friend bool operator>(const Date&, const Date&);
friend bool operator<=(const Date&, const Date&);
friend bool operator>=(const Date&, const Date&);
friend bool operator==(const Date&, const Date&);
friend bool operator!=(const Date&, const Date&);
friend Duration duration(const Date&, const Date&);
};
#endif // DATE1_H ///:~
 

Before you implement this class, you can solidify your grasp of the requirements by writing the beginnings of a test program. You might come up with something like the following:

//: C02:SimpleDateTest.cpp
//{L} Date
#include <iostream>
#include "Date.h" // From Appendix B
using namespace std;
 
// Test machinery
int nPass = 0, nFail = 0;
void test(bool t) { if(t) nPass++; else nFail++; }
 
int main() {
Date mybday(1951, 10, 1);
test(mybday.getYear() == 1951);
test(mybday.getMonth() == 10);
test(mybday.getDay() == 1);
cout << "Passed: " << nPass << ", Failed: "
<< nFail << endl;
}
/* Expected output:
Passed: 3, Failed: 0
*/ ///:~
 

In this trivial case, the function test( ) maintains the global variables nPass and nFail. The only visual inspection you do is to read the final score. If a test failed, a more sophisticated test( ) displays an appropriate message. The framework described later in this chapter has such a test function, among other things.

You can now implement enough of the Date class to get these tests to pass, and then you can proceed iteratively until all the requirements are met. By writing tests first, you are more likely to think of corner cases that might break your upcoming implementation, and you re more likely to write the code correctly the first time. Such an exercise might produce the following version of a test for the Date class:

//: C02:SimpleDateTest2.cpp
//{L} Date
#include <iostream>
#include "Date.h"
using namespace std;
 
// Test machinery
int nPass = 0, nFail = 0;
void test(bool t) { if(t) ++nPass; else ++nFail; }
 
int main() {
Date mybday(1951, 10, 1);
Date today;
Date myevebday("19510930");
 
// Test the operators
test(mybday < today);
test(mybday <= today);
test(mybday != today);
test(mybday == mybday);
test(mybday >= mybday);
test(mybday <= mybday);
test(myevebday < mybday);
test(mybday > myevebday);
test(mybday >= myevebday);
test(mybday != myevebday);
 
// Test the functions
test(mybday.getYear() == 1951);
test(mybday.getMonth() == 10);
test(mybday.getDay() == 1);
test(myevebday.getYear() == 1951);
test(myevebday.getMonth() == 9);
test(myevebday.getDay() == 30);
test(mybday.toString() == "19511001");
test(myevebday.toString() == "19510930");
 
// Test duration
Date d2(2003, 7, 4);
Date::Duration dur = duration(mybday, d2);
test(dur.years == 51);
test(dur.months == 9);
test(dur.days == 3);
 
// Report results:
cout << "Passed: " << nPass << ", Failed: "
<< nFail << endl;
} ///:~
 

This test can be more fully developed. For example, we haven t tested that long durations are handled correctly. We ll stop here, but you get the idea. The full implementation for the Date class is available in the files Date.h and Date.cpp in the appendix.[23]

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire