如何在 Vim 中实践 Python TDD
-- xyb [DateTime(2004-09-22T03:59:57Z)] TableOfContents
如何在 Vim 中实践 Python TDD
作者:Xie Yanbo,版权:创作共用/cc 1.0
TDD,Test Driver Development,中文叫做测试驱动开发。这是最近几年比较受推崇的开发模式,也是 XP(敏捷开发方法) 中的重要组成部分。
关于 TDD 的书籍现在中文版的也有了,相关概念可以去看书或在网上查找。
在 Python 中,也提供了 xUnit 的 Python 实现:[http://pyunit.sourceforge.net/ PyUnit],在源代码中是一个叫做 unittest 的模块,它提供了对单元测试的支持。还有许多使用 Python 的项目,都利用 PyUnit 开发了自己更适用、更方便的测试框架,比如 Twisted 有 trail,Zope 有 ZopeTestCase,Plone 有 PloneTestCase。关于 PyUnit 的一些基本知识请参考 PyUnitTut。
这个文档着重于讨论如何在 Vim 编辑器中使用 unittest 来实践 TDD。
首先要配置我们的 Vim,使之更适合 Python TDD。详细资料请参考 VimPython。
我们开发的案例是一个产品库,用来保存不同产品、不同版本的源代码。我们知道,开源社区有大量优秀的项目供我们无偿使用(感谢那些开发者),那些使用广泛的项目大多更新迅速,这是开源的优点,但也给使用者带来一些更新上的麻烦。比如我开发的 Zope 系统,每天都有新版本的第三方代码出现,我在自己的程序中使用了很多,每次当我制造发布包都是一件麻烦的事情。所以我想到,应该有一个保存这些不同产品、不同版本源代码的产品库,可以在我需要时很快的找到它们。当然我们这里讨论的只是一个 TDD 方法的介绍,不可能把整个系统都写完,但如果有兴趣的朋友可以把这个程序继续开发下去,为大家造福。如果我有时间或空闲,说不定我也会继续它的开发的
首先我们要确定需求。需求当然越详细越好,但我们自己的程序往往刚开始只有一个想法,TDD 对这个情况照样也很适应。我想从最基本的功能来说,这个产品库肯定要有添加产品的某个版本文件的功能,也要有从库里把这个产品、这个版本取出来的能力──这是最核心的功能了。
我们先来做入库的功能。我们开启 vim,建立一个新文件 test_filelib.py:
vim test_filelib.py
看到了吗,在 TDD 中,必须先写测试、再去编写它的实现。这个文件最开始只是一个空空的框架文件:
1 #!/usr/bin/env python
2 # -*- coding: GB2312 -*-
3
4 from unittest import TestCase
5
6 class simpleTest(TestCase):
7 def setUp(self):
8 pass
9
10 def tearDown(self):
11 pass
12
13 def testExample(self):
14 self.assertEqual(1, 1)
15
16 if '__main__' == __name__:
17 import unittest
18 unittest.main()
添加我们的入库测试代码,把做为例子的 testExample 删掉,我想这个入库的函数应该是这样使用和测试的: {{ #!python
- def testAddProduct(self):
- result = addProduct('myproduct', '0.1', 'myproduct-0.1.tgz')
self.assertEqual(result <> None, True)
- result = addProduct('myproduct', '0.1', 'myproduct-0.1.tgz')
}}}
在还没有考虑到任何实现方法的情况下,我认为应该把产品名、版本号、要加入的文件的名字传给入库方法。不管它是否真的能这么做,反正我现在是这么想的。好,现在我们执行一下这个测试:在 Vim 的命令模式下键入 :make。如果你使用的是 Gvim,可以在工具栏上找到一个 make 的快捷按钮,点击它。在我的 Vim 里,出现了这样的提示:
:!python test_filelib.py 2>&1| tee /tmp/v874609/3 E ====================================================================== ERROR: testAddProduct (__main__.simpleTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_filelib.py", line 20, in testAddProduct result = addProduct('myproduct', '0.1', 'myproduct-0.1.tgz') NameError: global name 'addProduct' is not defined ---------------------------------------------------------------------- Ran 1 test in 0.002s FAILED (errors=1) (6 / 11): NameError: global name 'addProduct' is not defined
看来它在告诉我,我还没有定义这个 addProduct 这个方法呐!呵呵,不要担心,在 TDD 看来,这是正常的。TDD 的原则就是:测试先行!当我们准备好了测试,才会去编写它的编码。好了,我们现在知道 test_filelib.py 是语法无误的,它可以编译运行,那我们就开始编写代码的实现。先在测试文件中写下导入的命令:
from filelib import *
然后在 Vim 的命令模式键入命令:
:new filelib.py
这时 Vim 中出现了一个新的窗口,其中编辑的是我们的新文件 filelib.py,我们键入如下代码:
# -*- coding: GB2312 -*- """管理不同产品、不同版本的文件库 """ def addProduct(ProductName, Version, Filename): return True
执行 :make 操作,这时 Vim 提示:
:!python ./alltests.py 2>&1| tee /tmp/v878704/2 python: can't open file './alltests.py' (1 / 1): python: can't open file './alltests.py'
原来我们忘了加上 alltests.py,文件,好的,新增一个 alltests.py 文件,并把测试 test_filelib 写入它的测试列表中:
1 #!/usr/bin/env python
2 # -*- coding: GB2312 -*-
3
4 import unittest
5 import sys
6 sys.path.append('./')
7 sys.path.append('../')
8 sys.path.append('./tests')
9
10 modules_to_test = (
11 'test_filelib',
12 )
13
14 def suite():
15 alltests = unittest.TestSuite()
16 for module in map(__import__, modules_to_test):
17 alltests.addTest(unittest.findTestCases(module))
18 return alltests
19
20 if __name__ == '__main__':
21 unittest.main(defaultTest='suite')
关闭 alltests.py,回到 filelib.py 中,重新执行 :make,这次的提示很完美:
:!python ./alltests.py 2>&1| tee /tmp/v878704/5 . ---------------------------------------------------------------------- Ran 1 test in 0.001s OK (1 / 5): .
测试报告告诉我们,我们的第一个测试已经通过!
也许有人要说了,这是在编程序吗?呵呵,TDD 的另一个原则:快速实现。快速实现允许你先不去考虑那么多,用一个最快的方法使测试通过,不管那个方法是多么可笑、多么简陋。但要记得,它是要与 TDD 的另一个原则一起使用,才能保证你的代码质量:重构。要记得随时随地审视代码,考虑它的更简洁、更合理的实现。现在我们就来重构一下这个函数。先得把它存到一个地方:
待续……