# 習題 47: 自動化測試
為了確認游戲的功能是否正常,你需要一遍一遍地在你的游戲中輸入命令。這個過程是很枯燥無味的。如果能寫一小段代碼用來測試你的代碼豈不是更好?然后只要你對程序做了任何修改,或者添加了什么新東西,你只要“跑一下你的測試”,而這些測試能確認程序依然能正確運行。這些自動測試不會抓到所有的 bug,但可以讓你無需重復輸入命令運行你的代碼,從而為你節約很多時間。
從這一章開始,以后的練習將不會有“你應該看到的結果”這一節,取而代之的是一個“你應該測試的東西”一節。從現在開始,你需要為自己寫的所有代碼寫自動化測試,而這將讓你成為一個更好的程序員。
我不會試圖解釋為什么你需要寫自動化測試。我要告訴你的是,你想要成為一個程序員,而程序的作用是讓無聊冗繁的工作自動化,測試軟件毫無疑問是無聊冗繁的,所以你還是寫點代碼讓它為你測試的更好。
這應該是你需要的所有的解釋了。因為你寫單元測試的原因是讓你的大腦更加強健。你讀了這本書,寫了很多代碼讓它們實現一些事情。現在你將更進一步,寫出懂得你寫的其他代碼的代碼。這個寫代碼測試你寫的其他代碼的過程將強迫你清楚的理解你之前寫的代碼。這會讓你更清晰地了解你寫的代碼實現的功能及其原理,而且讓你對細節的注意更上一個臺階。
### 撰寫測試用例
我們將拿一段非常簡單的代碼為例,寫一個簡單的測試,這個測試將建立在上節我們創建的項目骨架上面。
首先從你的項目骨架創建一個叫做 ex47 的項目。確認該改名稱的地方都有改過,尤其是 tests/ex47_tests.py 這處不要寫錯,另外運行 nosetest 確認一下沒有錯誤信息。檢查一下 tests/skel_tests.pyc 這個文件,有的話就把它刪掉,這一點需要尤其注意。
接下來創建一個簡單的 ex47/game.py 文件,里邊放一些用來被測試的代碼。我們現在放一個傻乎乎的小 class 進去,用來作為我們的測試對象:
<table class="highlighttable"><tbody><tr><td class="linenos"> <div class="linenodiv"> <pre> 1
2
3
4
5
6
7
8
9
10
11
12</pre> </div> </td> <td class="code"> <div class="highlight"> <pre>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)
</pre> </div> </td> </tr></tbody></table>
準備好了這個文件,接下來把測試骨架改成這樣子:
<table class="highlighttable"><tbody><tr><td class="linenos"> <div class="linenodiv"> <pre> 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</pre> </div> </td> <td class="code"> <div class="highlight"> <pre>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)
</pre> </div> </td> </tr></tbody></table>
這個文件 import 了你在 ex47.game 創建的 Room 這個類,接下來我們要做的就是測試它。于是我們看到一系列的以 test_ 開頭的測試函數,它們就是所謂的“測試用例(test case)”,每一個測試用例里面都有一小段代碼,它們會創建一個或者一些房間,然后去確認房間的功能和你期望的是否一樣。它測試了基本的房間功能,然后測試了路徑,最后測試了整個地圖。
這里最重要的函數時 assert_equal,它保證了你設置的變量,以及你在 Room 里設置的路徑和你的期望相符。如果你得到錯誤的結果的話, nosetests 將會打印出一個錯誤信息,這樣你就可以找到出錯的地方并且修正過來。
### 測試指南
在寫測試代碼時,你可以照著下面這些不是很嚴格的指南來做:
1. 測試腳本要放到 tests/ 目錄下,并且命名為 BLAH_tests.py ,否則 nosetests 就不會執行你的測試腳本了。這樣做還有一個好處就是防止測試代碼和別的代碼互相混掉。
1. 為你的每一個模組寫一個測試。
1. 測試用例(函數)保持簡短,但如果看上去不怎么整潔也沒關系,測試用例一般都有點亂。
1. 就算測試用例有些亂,也要試著讓他們保持整潔,把里邊重復的代碼刪掉。創建一些輔助函數來避免重復的代碼。當你下次在改完代碼需要改測試的時候,你會感謝我這一條建議的。重復的代碼會讓修改測試變得很難操作。
1. 最后一條是別太把測試當做一回事。有時候,更好的方法是把代碼和測試全部刪掉,然后重新設計代碼。
### 你應該看到的結果
~~~
~/projects/simplegame $ nosetests
...
----------------------------------------------------------------------
Ran 3 tests in 0.007s
OK
~~~
如果一切工作正常的話,你看到的結果應該就是這樣。試著把代碼改錯幾個地方,然后看錯誤信息會是什么,再把代碼改正確。
### 加分習題
1. 仔細讀讀 nosetest 相關的文檔,再去了解一下其他的替代方案。
1. 了解一下 Python 的 “doc tests” ,看看你是不是更喜歡這種測試方式。
1. 改進你游戲里的 Room,然后用它重建你的游戲,這次重寫,你需要一邊寫代碼,一邊把單元測試寫出來。
- 譯者前言
- 前言:笨辦法更簡單
- 習題 0: 準備工作
- 習題 1: 第一個程序
- 習題 2: 注釋和井號
- 習題 3: 數字和數學計算
- 習題 4: 變量(variable)和命名
- 習題 5: 更多的變量和打印
- 習題 6: 字符串(string)和文本
- 習題 7: 更多打印
- 習題 8: 打印,打印
- 習題 9: 打印,打印,打印
- 習題 10: 那是什么?
- 習題 11: 提問
- 習題 12: 提示別人
- 習題 13: 參數、解包、變量
- 習題 14: 提示和傳遞
- 習題 15: 讀取文件
- 習題 16: 讀寫文件
- 習題 17: 更多文件操作
- 習題 18: 命名、變量、代碼、函數
- 習題 19: 函數和變量
- 習題 20: 函數和文件
- 習題 21: 函數可以返回東西
- 習題 22: 到現在你學到了哪些東西?
- 習題 23: 讀代碼
- 習題 24: 更多練習
- 習題 25: 更多更多的練習
- 習題 26: 恭喜你,現在可以考試了!
- 習題 27: 記住邏輯關系
- 習題 28: 布爾表達式練習
- 習題 29: 如果(if)
- 習題 30: Else 和 If
- 習題 31: 作出決定
- 習題 32: 循環和列表
- 習題 33: While 循環
- 習題 34: 訪問列表的元素
- 習題 35: 分支和函數
- 習題 36: 設計和調試
- 習題 37: 復習各種符號
- 習題 38: 閱讀代碼
- 習題 39: 列表的操作
- 習題 40: 字典, 可愛的字典
- 習題 41: 來自 Percal 25 號行星的哥頓人(Gothons)
- 習題 42: 物以類聚
- 習題 43: 你來制作一個游戲
- 習題 44: 給你的游戲打分
- 習題 45: 對象、類、以及從屬關系
- 習題 46: 一個項目骨架
- 習題 47: 自動化測試
- 習題 48: 更復雜的用戶輸入
- 習題 49: 創建句子
- 習題 50: 你的第一個網站
- 習題 51: 從瀏覽器中獲取輸入
- 習題 52: 創建你的 web 游戲
- 下一步
- 老程序員的建議