自动化测试
为确认游戏正常,你需要一遍一遍在游戏中输入命令,过程枯燥无味,若能写一段代码用来测试代码是极好的。这样只要你对程序做了任何修改,或添加了什么新东西,只要“跑一下测试”,而这些测试能确认程序依然能正确运行。这些自动测试不会抓到所有bug,但可让你无需重复输入命令运行你的代码,从而节省很多时间。
从现在开始,你需要为自己写的所有代码写自动测试,这会让你成为更好的程序员。程序的作用是让无聊冗繁的工作自动化,让测试自动化是很好的。
写单元测试的原因是让代码更加强健,你需要写出懂得你写的其他代码的代码。写代码测试你写的其他代码的过程将强迫你清楚的理解你之前写的代码。会让你更清晰的了解你写的代码实现的功能及其原理,从而让你对细节的注意程度更上一个台阶。
编写测试用例
以一段非常简单的代码为例,写一个简单的测试,这个测试将建立在上一个习题中我们创建的项目骨架上。
首先,从你的项目骨架创建一个叫ex47的项目。下面要采取的步骤将用文字给出指令而不是展示如何录入它们,你必须理解这一点。
1. 复制骨架到ex47中。
2. 将带有NAME的东西都重命名为ex47.
3. 文件中的NAME全部改为ex47.
4. 删除所有*.py文件,确保已经清理干净。
注意 请记住,你要运行nosetests来运行测试。你可以通过python ex46-tests.py来运行它们,但这不容易工作,你不得不为每一个测试文件做一次。
若遇到困难,回去看看ex46.若完成这些还是不容易,需要多练习几次。
接下来创建一个简单的ex47/game.py文件,里边放一些要测试的代码。现在放一个小类进去做测试对象:
# game.py
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)
准备好这个文件,接下来把单元测试骨架改成下面的样子:
# ex47_tests.py
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('down').go('up'), start)
这个文件导入了你在ex47.game模块中创建的Room类,接下来就是测试它。于是我们看到一系列以test_开头的测试函数,它们就是所谓的“测试用例”(test case),每一个测试用例里面都有一小段代码,它们会创建一个或一些房间,然后确认房间的功能和你预期的是否一样。它测试了基本的房间功能,然后测试了路径,最后测试了整个地图。
这里最重要的函数是assert_equal,它保证了你设置的变量以及你在Room里设置的路径和你的期望相符。若得到错误结果,nosetests将会打印出一个错误信息,这样就可找到出错的地方并修正过来了。
测试指南
写测试代码时,你可照下面不是很严格的指南来做。
1. 测试脚本要放到tests/目录下,并且命名为BLAH_tests.py, 否则nosetests就不会执行你的测试脚本了。这样做还有一个好处就是防止测试代码和别的代码互相混掉。
2. 为你的每一个模块写一个测试
3. 测试用例(函数)保持简短,但若看上去不怎么整洁也没关系,测试用例一般都有点乱
4. 就算测试用例乱,也要试着让其保持整洁,把里边重复代码删掉。创建一些辅助函数来避免重复的代码。当你下次再改完代码需要改测试的时候,你会感谢这种做法的。重复代码会让修改测试变得很难操作。
5. 最后一条别把测试太当回事。有时更好的方法是把代码和测试全部删掉,然后重新设计代码。
应该看到的结果
$ nosetests
...
-------------------------------------------------------------
Ran 3 tests in 0.008s
OK若一切正常,结果如上,试着将代码改错几个地方,然后看错误信息是什么,再把代码改正确。
附加练习
1. 仔细读读与nosetests相关的文档,再去了解一下其他的替代方案
2. 了解一下Python的“doctest”,看看你是不是更喜欢这种测试方式
3. 改进你游戏里的Room, 然后用它重建你的游戏,这次重写,你需要一边写代码,一边把单元测试写出来。
常见问题回答
运行nosetests时出现语法错误?
看看错误信息具体内容,把对应的语法错误改正过来。nosetests这类工具会运行你写的程序代码和测试代码,所以和Python一样,它也会找出你的语法错误。
无法导入ex47.game?
确认你创建了ex47/__init__.py文件,回到前面的内容看看如何创建。
运行nosetests时看到UserWarning?
你也许装了两个版本的Python,或者你没使用distribute,照着习题46装一下distribute或者pip就可以了。