很多软件都实现了日期这一功能,日历系统算是人类的一个legacy system。在此系统逐步进化中,也衍生出一些问题,例如“千年虫”、”闰年虫”。其中闰年虫是人们对于一些电脑在设计时未考虑闰年因素,将所有年份的2月都默认为有29天或者28天而出现运算错误的一种形象叫法,因为在英文里Bug兼有臭虫、缺陷等含义,所以这一缺陷被称为“闰年虫”。
在2012年2月底,广州“闰年虫”爆发,上千的士因计价器出问题而停运,如图所示:
就连著名的微软也会出现”闰年虫”的失误,微软Windows Azure云平台若干子区域受“闰年虫”影响致许多客户12至24个小时无法使用服务。根据Windows Azure服务仪表板显示,从UTC时间2012年2月29日凌晨到3月1日早上,大量的子区域服务和全球性服务发生了超过24小时的中断。
因此如何正确判断闰年是一个十分重要的问题,下面是C# 的代码片段:
1 public static bool IsLeapYear(int year) 2 { 3 System.Diagnostics.Debug.Assert(year >= 1900); 4 if (year % 400 == 0) 5 return true; 6 if (year % 100 == 0) 7 return false; 8 if (year % 4 == 0) 9 return true; 10 return false; 11 }
如果你要写这个程序的单元测试, 你会列出多少个测试用例? 一个”笨”方法就是进行穷举,可又会面临以下问题:
- 穷举不完
- 即使穷举了很多例子, 但是它们未必能帮助发现独特的问题,并不具有代表性
因而我们要引入 “等价类 (Equivalence)” 这一概念。所谓等价类是指输入域的某个互不相交的子集合,所有等价类的并集便是整个输入域,目的在于测试用例的无冗余性。 一个粗浅的做法是:
如果一个函数可以返回 true | false, 你至少得有两类测试集合, 让它分别返回 true | false
如果你知道这个函数工作的原理, 或者了解程序要反映的现实世界, 你可以举出更详细的等价类, 例如针对 IsLeapYear()的测试用例如下:
被 400 整除的年份
被 100 整除, 但是不被400 整除的年份
被 100 整除, 同时被400 整除的年份
被 4 整除, 但是不被100 整除的年份
被 4 整除, 同时被100 整除的年份
偶数, 不被4 整除的年份
奇数年份
其它非法输入的年份
另外程序员都知道程序经常在边界条件附近出错, 针对IsLeapYear(), 你可以得出下面两个测试用例:
设计允许的最小的年份
设计允许的最大的年份
总而言之,选取正确而又合适的测试用例是测试程序有无bug的必备条件。除此之外,判断是否为闰年的代码也可以简化如下:
1 bool isLeapYear( int year ) 2 { 3 return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0); 4 }