为了确认游戏的功能是否正常,你需要一遍一遍地在你的游戏中输入命令。这个过程是很枯燥无味的。如果能写一小段代码用来测试你的代码岂不是更好?然后只要你对程序做了任何修改,或者添加了什么新东西,你只要“跑一下你的测试”,而这些测试能确认程序依然能正确运行。这些自动测试不会抓到所有的 bug,但可以让你无需重复输入命令运行你的代码,从而为你节约很多时间。
从这一章开始,以后的练习将不会有“你应该看到的结果”这一节,取而代之的是一个“你应该测试的东西”一节。从现在开始,你需要为自己写的所有代码写自动化测试,而这将让你成为一个更好的程序员。
我不会试图解释为什么你需要写自动化测试。我要告诉你的是,你想要成为一个程序员,而程序的作用是让无聊冗繁的工作自动化,测试软件毫无疑问是无聊冗繁的,所以你还是写点代码让它为你测试的更好。
这应该是你需要的所有的解释了。因为你写单元测试的原因是让你的大脑更加强健。你读了这本书,写了很多代码让它们实现一些事情。现在你将更进一步,写出懂得你写的其他代码的代码。这个写代码测试你写的其他代码的过程将强迫你清楚的理解你之前写的代码。这会让你更清晰地了解你写的代码实现的功能及其原理,而且让你对细节的注意更上一个台阶。
我们将拿一段非常简单的代码为例,写一个简单的测试,这个测试将建立在上节我们创建的项目骨架上面。
首先从你的项目骨架创建一个叫做 ex47 的项目。确认该改名称的地方都有改过,尤其是 tests/ex47_tests.py 这处不要写错,另外运行 nosetest 确认一下没有错误信息。检查一下 tests/skel_tests.pyc 这个文件,有的话就把它删掉,这一点需要尤其注意。
接下来创建一个简单的 ex47/game.py 文件,里边放一些用来被测试的代码。我们现在放一个傻乎乎的小 class 进去,用来作为我们的测试对象:
1 2 3 4 5 6 7 8 9 10 11 12 | class Room(object):
def __init__(self, name, description):
self.name = name
self.description = description
self.paths = {}
def go(self, direction):
return self.paths.get(direction, None)
def add_paths(self, paths):
self.paths.update(paths)
|
准备好了这个文件,接下来把测试骨架改成这样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | from nose.tools import *
from ex47.game import Room
def test_room():
gold = Room("GoldRoom",
"""This room has gold in it you can grab. There's a
door to the north.""")
assert_equal(gold.name, "GoldRoom")
assert_equal(gold.paths, {})
def test_room_paths():
center = Room("Center", "Test room in the center.")
north = Room("North", "Test room in the north.")
south = Room("South", "Test room in the south.")
center.add_paths({'north': north, 'south': south})
assert_equal(center.go('north'), north)
assert_equal(center.go('south'), south)
def test_map():
start = Room("Start", "You can go west and down a hole.")
west = Room("Trees", "There are trees here, you can go east.")
down = Room("Dungeon", "It's dark down here, you can go up.")
start.add_paths({'west': west, 'down': down})
west.add_paths({'east': start})
down.add_paths({'up': start})
assert_equal(start.go('west'), west)
assert_equal(start.go('west').go('east'), start)
assert_equal(start.go('down').go('up'), start)
|
这个文件 import 了你在 ex47.game 创建的 Room 这个类,接下来我们要做的就是测试它。于是我们看到一系列的以 test_ 开头的测试函数,它们就是所谓的“测试用例(test case)”,每一个测试用例里面都有一小段代码,它们会创建一个或者一些房间,然后去确认房间的功能和你期望的是否一样。它测试了基本的房间功能,然后测试了路径,最后测试了整个地图。
这里最重要的函数时 assert_equal,它保证了你设置的变量,以及你在 Room 里设置的路径和你的期望相符。如果你得到错误的结果的话, nosetests 将会打印出一个错误信息,这样你就可以找到出错的地方并且修正过来。
在写测试代码时,你可以照着下面这些不是很严格的指南来做:
~/projects/simplegame $ nosetests
...
----------------------------------------------------------------------
Ran 3 tests in 0.007s
OK
如果一切工作正常的话,你看到的结果应该就是这样。试着把代码改错几个地方,然后看错误信息会是什么,再把代码改正确。
运行 nosetests 时出现语法错误(SyntaxError)。
看看错误信息的具体内容,把对应哪行的语法错误改正过来。 nosetests 这类工具会运行 你写的程序代码以及测试代码,所以和 python 一样,它也会找出你的语法错误。
无法 import ex47.game?
确认你创建了 ex47/__init__.py 文件,回到前面的内容看看如何创建。
运行 nosetests 时看到 UserWarning。
你也许装了两个版本的 Python,或者你不是用的 distribute,回去照着《习题 46》装一下 distribute or pip 就可以了。