C++编程思想 第2卷 第2章 防御性编程 一个简单的单元测试框架 自动测试

单元测试是什么样子的?
有太多的开发人员常常只希望他们的代码在获得符合要求的输入时能够产生
预期的输出,他们只是用眼睛检查程序的输出
方法存在危险,首先,程序的输入不可能总是符合要求

其次是用眼睛观察来检查程序的输出,是很乏味的而且容易出错
计算机可以检查程序的输出,并且不会出错。

最好用布尔表达式的集合来标识错误问题,让测试程序报告代码的任何错误

假设构造一个Date类,这个类有以下特性:
可以用一个字符串 YYYYMMDD, 三个整数 YMD 或者什么也不用 获得当前日期
来初始化日期值
Date对象能够生成年、月、日的值或 YYYYMMDD 形式的字符串
所有相关量能够进行有效的比较,能够计算两个日期的差 在年、月、日 中
日期的比较能够跨越任意个世纪 如1600和2200

这个类能够用三个整数分别表示 年、月、日

//: C02:Date.h
// From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison.
// (c) 1995-2004 MindView, Inc. All Rights Reserved.
// See source code use permissions stated in the file 'License.txt',
// distributed with the code package available at www.MindView.net.
#ifndef DATE_H
#define DATE_H
#include <string>
#include <stdexcept>
#include <iosfwd>

class Date {
  int year, month, day;
  int compare(const Date&) const;
  static int daysInPrevMonth(int year, int mon);
public:
  // A class for date calculations
  struct Duration {
    int years, months, days;
    Duration(int y, int m, int d)
    : years(y), months(m) ,days(d) {}
  };
  // An exception class
  struct DateError : public std::logic_error {
    DateError(const std::string& msg = "")
      : std::logic_error(msg) {}
  };
  Date();
  Date(int, int, int) throw(DateError);
  Date(const std::string&) throw(DateError);
  int getYear() const;
  int getMonth() const;
  int getDay() const;
  std::string toString() const;
  friend Duration duration(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 bool operator!=(const Date&, const Date&);
  friend std::ostream& operator<<(std::ostream&,
                                  const Date&);
  friend std::istream& operator>>(std::istream&, Date&);
};
#endif // DATE_H ///:~

贴出了代码 Date.cpp 要不通不过 链接

//: C02:Date.cpp {O}
// From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison.
// (c) 1995-2004 MindView, Inc. All Rights Reserved.
// See source code use permissions stated in the file 'License.txt',
// distributed with the code package available at www.MindView.net.
#include "Date.h"
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <string>
#include <algorithm> // For swap()
#include <ctime>
#include <cassert>
#include <iomanip>
using namespace std;

namespace {
  const int daysInMonth[][13] = {
    { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
    { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
  };
  inline bool isleap(int y) {
    return y%4 == 0 && y%100 != 0 || y%400 == 0;
  }
}

Date::Date() {
  // Get current date
  time_t tval = time(0);
  struct tm *now = localtime(&tval);
  year = now->tm_year + 1900;
  month = now->tm_mon + 1;
  day = now->tm_mday;
}

Date::Date(int yr,int mon,int dy) throw(Date::DateError) {
  if(!(1 <= mon && mon <= 12))
    throw DateError("Bad month in Date ctor");
  if(!(1 <= dy && dy <= daysInMonth[isleap(year)][mon]))
    throw DateError("Bad day in Date ctor");
  year = yr;
  month = mon;
  day = dy;
}

Date::Date(const std::string& s) throw(Date::DateError) {
  // Assume YYYYMMDD format
  if(!(s.size() == 8))
    throw DateError("Bad string in Date ctor");
  for(int n = 8; --n >= 0;)
    if(!isdigit(s[n]))
      throw DateError("Bad string in Date ctor");
  string buf = s.substr(0, 4);
  year = atoi(buf.c_str());
  buf = s.substr(4, 2);
  month = atoi(buf.c_str());
  buf = s.substr(6, 2);
  day = atoi(buf.c_str());
  if(!(1 <= month && month <= 12))
    throw DateError("Bad month in Date ctor");
   if(!(1 <= day && day <=
     daysInMonth[isleap(year)][month]))
    throw DateError("Bad day in Date ctor");
}

int Date::getYear() const { return year; }

int Date::getMonth() const { return month; }

int Date::getDay() const { return day; }

string Date::toString() const {
  ostringstream os;
  os.fill('0');
  os << setw(4) << year
     << setw(2) << month
     << setw(2) << day;
  return os.str();
}

int Date::compare(const Date& d2) const {
  int result = year - d2.year;
  if(result == 0) {
    result = month - d2.month;
    if(result == 0)
      result = day - d2.day;
  }
  return result;
}

int Date::daysInPrevMonth(int year, int month) {
  if(month == 1) {
    --year;
    month = 12;
  }
  else
    --month;
  return daysInMonth[isleap(year)][month];
}

bool operator<(const Date& d1, const Date& d2) {
  return d1.compare(d2) < 0;
}
bool operator<=(const Date& d1, const Date& d2) {
  return d1 < d2 || d1 == d2;
}
bool operator>(const Date& d1, const Date& d2) {
  return !(d1 < d2) && !(d1 == d2);
}
bool operator>=(const Date& d1, const Date& d2) {
  return !(d1 < d2);
}
bool operator==(const Date& d1, const Date& d2) {
  return d1.compare(d2) == 0;
}
bool operator!=(const Date& d1, const Date& d2) {
  return !(d1 == d2);
}

Date::Duration
duration(const Date& date1, const Date& date2) {
  int y1 = date1.year;
  int y2 = date2.year;
  int m1 = date1.month;
  int m2 = date2.month;
  int d1 = date1.day;
  int d2 = date2.day;

  // Compute the compare
  int order = date1.compare(date2);
  if(order == 0)
    return Date::Duration(0,0,0);
  else if(order > 0) {
    // Make date1 precede date2 locally
    using std::swap;
    swap(y1, y2);
    swap(m1, m2);
    swap(d1, d2);
  }

  int years = y2 - y1;
  int months = m2 - m1;
  int days = d2 - d1;
  assert(years > 0 ||
     years == 0 && months > 0 ||
     years == 0 && months == 0 && days > 0);

  // Do the obvious corrections (must adjust days
  // before months!) - This is a loop in case the
  // previous month is February, and days < -28.
  int lastMonth = m2;
  int lastYear = y2;
  while(days < 0) {
    // Borrow from month
    assert(months > 0);
    days += Date::daysInPrevMonth(
      lastYear, lastMonth--);
    --months;
  }

  if(months < 0) {
    // Borrow from year
    assert(years > 0);
    months += 12;
    --years;
  }
  return Date::Duration(years, months, days);
}

ostream& operator<<(ostream& os, const Date& d) {
  char fillc = os.fill('0');
  os << setw(2) << d.getMonth() << '-'
     << setw(2) << d.getDay() << '-'
     << setw(4) << setfill(fillc) << d.getYear();
  return os;
}

istream& operator>>(istream& is, Date& d) {
  is >> d.month;
  char dash;
  is >> dash;
  if(dash != '-')
    is.setstate(ios::failbit);
  is >> d.day;
  is >> dash;
  if(dash != '-')
    is.setstate(ios::failbit);
  is >> d.year;
  return is;
} ///:~

编写测试程序

//: C02:SimpleDateTest.cpp
// From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison.
// (c) 1995-2004 MindView, Inc. All Rights Reserved.
// See source code use permissions stated in the file 'License.txt',
// distributed with the code package available at www.MindView.net.
//{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;
  getchar();
}
/* Expected output:
Passed: 3, Failed: 0
*/ ///:~

在测试中,函数test()维护着两个全局变量nPass和nFail


输出
Passed: 3, Failed: 0

可以逐步实现Date类,通过测试,然后可以继续进行反复测试直到满足所有
的要求

由于先编写测试程序,程序员会更多注意考虑其他边边角角的情况,这些
情况会破坏即将实现的程序,使程序员在第一时间就更加注意编写正确的
代码

把SimpleDateTest.cpp 换成 SimpleDateTest2.cpp

//: C02:SimpleDateTest2.cpp
// From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison.
// (c) 1995-2004 MindView, Inc. All Rights Reserved.
// See source code use permissions stated in the file 'License.txt',
// distributed with the code package available at www.MindView.net.
//{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;
  getchar();
} ///:~

这个测试可以开发得更完整。例如测试程序的可持续性

输出
Passed: 21, Failed: 0

猜你喜欢

转载自blog.csdn.net/eyetired/article/details/81588189