现代软件工程讲义 9 测试 关于闰年的测试

这是现代软件工程讲义的一部分

我们谈了不少测试的名词, 规范和原则 (link1, link2). 软件是人写的, 测试计划和测试用例也是人写的, 人总会犯错误。错误发生之后, 总有人问: 为什么这个bug 没有测出来啊?!   我们看看一类简单的bug是如何发生的,以及如何预防它们再度发生: 

闰年

软件少不了和日期打交道, 日历系统算是人类的一个 legacy system, 这个系统在逐步进化的过程中, 打了好多补丁, 闰年就是补丁之一, 现在的spec 是: 4 年一闰, 100 年不闰,400年又闰。

错误之一: 算不清那一年是不是闰年。 1900 年是闰年么?

电子表格软件Excel 就有这样一个Bug:Excel 的日期计算功能认为1900年是一个闰年,这是不对的,但是它愣是一直没有改正这个错误。为什么屡教不改呢?

故事是这样的,1980 年代, 这类电子表格软件的市场领头羊是Lotus 1-2-3这一款软件。

来源: http://en.wikipedia.org/wiki/Lotus_1-2-3 

Lotus 1-2-3 占据了大部分市场份额, 不过, 它的日期计算功能有一个小Bug,就是把1900 年当作闰年。这类软件在内部把日期保存为 “从1900/1/1 到当前日期的天数” 这样的一个整数。Excel 作为后来者,要支持 Lotus 1-2-3 的数据文件格式,这样才能正确处理别的软件产生的格式文件。  这个错误就这么延续下来了,每一版本都有人报告,但是都没有改正。我们可以在Excel 中试试看:

在任意格子(cell)中输入“=DATE(1900,2,28)”,并且定义这个格子的格式为数字。大家可以看到数值变为:59。表明1900/2/28 是1900/1/1开始的第59天。

输入“=DATE(1900,2,29)”,可以看到 60! 这是一个不存在的日期!

输入“=DATE(1900,3,1)”,数值是61,事实上,这应该是60。从这一天开始的所有日期都错了一天。

改这个问题,技术上一点问题都没有。但是在现实中会出现下列问题:

(1)几乎所有现存文件的日期数据都要减少一天,所有依赖于日期的 Excel公式也要做检查和修改。可以想象在计算利率,判断日期是否相等这些问题上都会出现细小而不能忽视的问题。 这在现实生活会造成很大的麻烦。

(2)Excel的日期问题解决了,但是其他软件还是有这个Bug,数据文件在不同软件中使用,就会有很头痛的兼容性问题。

下面是C# 的代码片段, 这段程序对么?

public static bool IsLeapYear(int year)
{
    System.Diagnostics.Debug.Assert(year >= 1900);
    if (year % 400 == 0)
        return true;
    if (year % 100 == 0)
        return false;
    if (year % 4 == 0)
        return true;
    return false;
}

错误之二: 计算错误

一个应用程序从另一个模块中接到一个数值, 是当天距离 [1980/1/1] 的天数, 现在要求这个程序返回今天的年份。 下面的程序怎么样? 有bug 么?

public static int NumberToYear(int days)
{
    int year = 1980; /* start with 1980 */
    System.Diagnostics.Debug.Assert(days >= 0);

    while (days > 365)
    {
        if (IsLeapYear(year))
        {
            if (days > 366)
            {
                days -= 366;
                year ++;
            }
        }
        else
        {
            days -= 365;
            year ++;
        }
    }
    return year;
}   

如果你要写这个程序的单元测试, 你会列出多少个测试用例? 你们保证所有代码路径都被覆盖么?

要写测试用例, 一个暴力的做法是穷举所有例子, 但是这有问题:

    1. 你穷举不完
    2. 即使穷举了很多例子, 但是它们未必能帮助发现 独特 的问题.  例如你可以测试输入 为 100, 101, 102, 103, 104, … 如果这个程序能正确处理 100, 它似乎也能处理101… 这些数。

我们要引入 “等价类 (Equivalence)” 这一概念。 一个粗浅的做法是:

如果一个函数可以返回 true | false, 你至少得有两类测试集合, 让它分别返回 true | false

如果你知道这个函数工作的原理, 或者了解程序要反映的现实世界, 你可以举出更详细的等价类, 例如针对 IsLeapYear():

被 400 整除的年份

被 100 整除, 但是不被400 整除的年份

被 100 整除, 同时被400 整除的年份

被 4 整除, 但是不被100 整除的年份

被 4 整除, 同时被100 整除的年份

偶数, 不被4 整除的年份

奇数年份

其它非法输入的年份

程序员都知道程序经常在边界条件附近出错, 针对IsLeapYear(), 你可以得出下面两个测试用例:

设计允许的最小的年份

设计允许的最大的年份

啊, 设计中没有考虑这个?   那这个设计要出现问题。  在1950-70 年代, 很多程序用两位数字表示年份 (00 – 99), 那些聪明的程序员认为这已经足够了, 没想到这些程序和设计影响了很多要和它们兼容的程序 (就像 Excel 要兼容 Lotus 1-2-3 那样), 到了1990年代后期, IT 业花了很多人力物力来解决 Y2K 的千年虫问题。 一些程序员非常钟爱的 UNIX 操作系统 (32 位) 也有自己的千年虫问题, 它会发生在 2038 年! 到时候人们还会用32位的机器么? 也许在一个大家想不到的关键部位, 一些老旧的, 嵌入式的 Unix 系统会悄悄地发作…

除了从外部的输入/输出来设计测试用例, 我们也可以从内部考虑, 看看这些测试用例是否把所有语句都覆盖了。 但是要注意, 即使所有语句都被测试用例覆盖了, 程序还是可能出错!

例如, 我们测试 NumberToYear(), 分析它的各个条件, 我们推算出我们的数据要覆盖下面一些情况:

输入的 day 大于 365

输入的 day 小于 365

输入的 day 大于 366  并且1980 年到那一年中, 至少有一年是闰年, 例如输入一个2008年的某一天。

输入的 day 大于 366  并且1980 年到那一年中不包括闰年。

这样是不是就把所有路径都包括了? 程序就没有错了?

不巧的是, 这个程序用在了某著名公司的产品上, 出品的前两年没什么事, 到了2008 年的最后一天 (那一年有366 天), 出了一个问题:

正如下面的代码显示的,年份一直增加到了 2008, 这时候, day == 366, 我们看看循环能做下去么?

if (IsLeapYear(year))
{
    if (days > 366)   //day == 366, 不满足条件 
    {
        days -= 366;
        year ++;
    }
}

所以 day 没有减少, year 也没有增加, 循环又继续下去, 任何条件都没有改变, 进入了死循环!

不幸的是, 这个程序经过了种种测试, 进入了市场.  于是, 在2008 年的最后一天, 许多用户发现他们的 Zune Player (只限于 Zune 30 型号) 开机之后就进入死锁状态…

Microsoft says Zune players working again - USATODAY.com

http://www.zuneboards.com/forums/showthread.php?t=38143

官方的说法是 - 大家等到明天就好了!     不用说这对于用户, 对于产品的口碑, 对于这个代码的开发者, 测试者是一个极大的打击!

也有程序员提出:

@bnu_chenshuo: 文中的函数可用一句话搞定

int NumberToYear(int days)
{       
    return 1980 + 100 * days / 36525;
}

大家觉得这个函数有没有什么 bug? 在今后的 100 年都可以使用么?
 

如果你是一个测试人员, 你应该增加什么测试用例呢?  如果用边界条件分析, 应该有至少 4 个新的测试用例:

闰年的第一天;

闰年的最后一天;

平年的第一天;

平年的最后一天

对于程序员, TA 应该如何修改代码呢?

错误之三: 没想到还有闰年

在IT 行业混了很多年的好处之一就是你可以看到不少 bug. 下面又来了一个:

Windows Home Server与客户端connector第一次连接时,需要Server为connector颁发安全证书。出于某种实现上无法避免的原因,客户端的证书日期一定要早于Windows Home Server,否则生成证书的函数会fail。Windows Home Server是2007年7月RTM的。为了方便起见,设计中规定,给客户端生成证书的函数使用2006年作为年份

<完整的故事 link: http://yishan.cc/blogs/lilei105/archive/2008/03/03/885.aspx>

作为一个程序员, 你如何实现这个设计呢? 一拍脑袋, 就取当天的日期, 然后把日期中的年字段改成 2006, 不就行了么?

然后到了 2008/2/29 这一天… 程序把日期改成了 2006/2/29. 然后就悲剧了.   

软件团队在自问: 为啥我们当初没测出来?  如果你是测试人员, 你会想到这个测试用例么?

错误之四: 闰年bug 一天损失 30 万

上面的错误都是外国软件公司搞的, 我们看看中国的软件 (还是嵌入式的软件) 也不甘落后, 也创出了自己的闰年bug

广州出租车计价器无法识别闰年 损失约30万(图)-搜狐新闻  

-------------------------------

参考阅读:

测试用例的等价类划分和边界条件分析:

http://en.wikipedia.org/wiki/Equivalence_partitioning

http://en.wikipedia.org/wiki/Boundary_value_analysis

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
软件测试技术实践考核上机练习题 软件测试技术实践考核上机考试基本要求(1004) 一、编程语言及上机环境 (1)C/C++编程语言 (2)VC++6.0及以上编译环境 二、考试内容 1、功能(黑盒)测试用例设计编程实现 (1)等价类划分法 (2)边界值分析法 (3)因果图法 (4)决策表法 2、结构(白盒)测试用例设计编程实现 (1)语句覆盖 (2)判定覆盖 (3)条件覆盖 (4)组合覆盖 (5)路径覆盖 (6)独立路径测试 三、上机考试程序 (1)考生抽取试题。 (2)排定考试座位(机位)。 (3)启动上机环境。 (4)开始考试。 (5)程序验收。 (6)适当的口试。 (7)成绩评定。 上机考试时间为120分钟。 上机考试成绩评定的依据主要是根据试题的完成情况和程序的运行结果,以及必要的口试。 四、考生注意事项 1、平时训练与考试 (1)思想重视 明确考试目的,端正考试态度,认真做好上机考试的准备工作。 (2)知识准备 平时认真学习,消化课程内容,熟悉编程环境和工具,认真做好课程实验。 (3)平时训练 应针对上机考试题型做好平时训练。 2、遵守考场纪律 对于下列情况之一者,实践课成绩为不及格。 (1)上机程序运行未通过。 (2)拷贝他人的上机程序。 (3)上机考试严重违纪。 软件测试技术实践考核上机考试练习题(1004) 练习题(一) 1、NextDate函数问题说明:输入一个日期,求从输入日期算起的第三天日期。例如,输入为2008年8月8日,则该程序的输出为2008年8月10日。NextDate函数包含三个整数变量month、day和year,并且满足下列条件:1≤ month ≤12、1≤ day ≤31和2000≤ year ≤2100。分析各种输入情况,列出为输入变量month、day、year划分的有效等价类: 输入等价类 输入 ID 有效等价类 day 1 1 ≤day≤26 2 day=27 3 day=28 4 day=29 5 day=30 6 day=31 month 7 month=4,6,9,11 8 month=1,3,5,7,8,10 9 month=2 10 month=12 year 11 闰年 12 非闰年
2.1黑盒测试 2.1.1 实验目的   (1) 能熟练应用黑盒测试技术进行测试用例设计;   (2) 对测试用例进行优化设计; 2.1.2 实验设备   主流 PC 机一套,要求安装windows 操作系统和Office 工具。 2.1.3 实验内容 题目一:日期问题   用决策表测试测试以下程序:该程序有三个输入变量month、day、year(month 、 day 和year均为整数值,并且满足:1800≤year≤2020,1≤month≤12 和1≤day≤31),分别作为输入日期的月份、日、年份,通过程序可以输出该输入日期在日历上前一天的日期。例如,输入为 2004 年11 月29 日,则该程序的输出为2004 年11 月28 日。   (1) 分析各种输入情况,列出为输入变量 month、day、year 划分的有效等价类。   (2) 分析程序的规格说明,并结合以上等价类划分的情况,给出问题规定的可能采取的操作(即列出所有的动作桩)。   (3) 根据 (1) 和 (2) ,画出简化后的决策表。 划分等价类(此处只考虑了有效等价类),基本思想是根据三个输入项的取值来划分,首先三个输条件都是整数,然后确定边界值,month[1,12],day[1,31],year[1800,2020],再来划分等价类,条件依据是:闰年的2月是29天,非闰年的2月时28天,{1,3,5,7,8,10,12}月份是31天,{2,4,6,9,11}月份是30天 等价类 Year Month Day 有效等价类 Y1:[1800,2020]且为闰年 M1:2 D1:27 Y2:[1800,2020]不为闰年 M2:1,3,5,7,8, 10, D2:28 M3:2,4,6,9,11 D3 :29 M4:12 D4 :30 D4 :31 所有动作桩(弱组合形式) 测试用例ID 测试用例的等价类构成 001 Y1 M1 D1 002 Y2 M2 D2 003 Y1 M3 D3 004 Y2 M4 D4 005 Y1 M1 D5 强组合形式有2*3*5=30种。 决策表 Year Y1,Y2 Y1,Y2 Y1,Y2 Y1,Y2 Y1,Y2 Y1,Y2 Y1 Y2 Y1 Y2 Y1,Y2 Y1,Y2 Month M1,M4 M1 M2 M2 M2 M3 M3 M3 M3 M3 M3 M4 Day D1-D4 D5 D1-D3 D4 D5 D1 D2 D2 D3 D3 D4,D5 D5 Day+1 ▲ ▲ ▲ ▲ Day=1 ▲ ▲ ▲ ▲ ▲ Month+1 ▲ ▲ ▲ ▲ Month=1 ▲ Year+1 ▲ Year not exist ▲ ▲

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值