4.3.5 自底向上实现与单元测试

自顶向下设计设计是创建层次化的模块结构的过程,而从实现的角度看,我们又是采取了相反的过程,即自底向上的实现。从结构图的底层开始实现每一个函数,然后上一层模块 自然得到实现。就这样自底向上,直至主程序得到完全的实现。

在模块化编程中,测试程序最适合采用单元测试技术,即先分别测试每一个小模块,然 后再逐步测试较大的模块,直至最后测试完整程序。以 calendar 程序为例,当我们实现了 days(y,m)函数后,就应该来测试此函数是否能完成预定的功能——返回 y 年 m+1 月有多 少天。我们可以将 days(y,m)的定义存入一个模块文件(假设文件名是 moduletest.py), 然后导入该文件并测试函数。下面是测试 days 函数的一个会话过程:

  1. >>> from moduletest import days
  2. >>> days(1900,0)
  3. 31
  4. >>> days(1900,1)
  5. 28
  6. >>> days(1900,11)
  7. 31
  8. >>> days(2000,1)
  9. 29
  10. >>> days(2012,1)
  11. 29
  12. >>> days(2012,10)
  13. 30

注意,测试时应当使测试数据尽量覆盖所有关键情形。在我们的测试例子中,测试了合 法数据的边界情形 1900 年 1 月,也测试了 1900 年 2 月(这个年份虽然能被 4 整除但却不是闰年),还测试了 2000 年(能被 400 整除)是否闰年。所有测试结果都表明这个函数实现正 确。

单元测试技术独立地测试每一个函数,这样能更容易定位程序错误。如果较小模块都正 确,那么由它们组成的较大模块出现错误的可能性也就较小。最终测试完整程序时,就更有 希望通过测试。

最后,为了完整起见,我们将前面所有的代码汇集起来列在下面。

【程序 4.8】calendar.py

  1. # calendar.py
  2. def getYear():
  3. print "This program prints the calendar of a given year."
  4. year = input("Please enter a year (after 1900): ")
  5. return year
  6. def firstDay(year):
  7. k = leapyears(year)
  8. n = (year - 1900) * 365 + k return (n + 1) % 7
  9. def leapyears(year): count = 0
  10. for y in range(1900,year):
  11. if y%4 == 0 and (y%100 != 0 or y%400 == 0):
  12. count = count + 1
  13. return count
  14. def printCalendar(year,w): print
  15. print "=========== " + str(year) + " =========="
  16. first = w
  17. for month in range(12):
  18. heading(month)
  19. first = oneMonth(year,month,first)
  20. def heading(m):
  21. months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"]
  22. print " %s " % (months[m])
  23. print "Mon Tue Wed Thu Fri Sat Sun"
  24. def oneMonth(year,month,first): d = days(year,month)
  25. frame = layout(first,d)
  26. printMonth(frame) return (first + d) % 7
  27. def days(y,m):
  28. month_days = [31,28,31,30,31,30,31,31,30,31,30,31]
  29. d = month_days[m]
  30. if (m == 1) and (y%4 == 0 and (y%100 != 0 or y%400 == 0)):
  31. d = d + 1
  32. return d
  33. def layout(first,d): frame = 42 * [""]
  34. if first == 0:
  35. first = 7
  36. j = first - 1
  37. for i in range(1,d+1):
  38. frame[j] = i
  39. j = j + 1
  40. return frame
  41. def printMonth(frame):
  42. for i in range(42):
  43. print "%3s" % (frame[i]),
  44. if (i+1)%7 == 0:
  45. print
  46. def main():
  47. year = getYear()
  48. w = firstDay(year)
  49. printCalendar(year,w)
  50. main()

图 4.11 显示了本程序的一次运行结果,可见程序是正确的(注意 2012 是闰年)。当然, 输出的日历在格式上还可以美化,例如将两三个月的日历放在同一排上之类。读者不妨自行 设计修改。

4.3.5 自底向上实现与单元测试 - 图1

图 4.11 calendar 程序的运行示例