阅读更多
摘要:Python是一种敏捷开发项目经常采用的语言。本文介绍了在Python敏捷项目中结合类似epydoc文档系统,使在doctest单元测试同时能够产生同步的最新文档,比如常用的功能说明文档等,在敏捷开发中常被称作敏捷文档。
关键词:Python;敏捷开发;敏捷文档;doctest;单元测试;执行文档;epydoc
中图分类号:TP311文献标识码:A文章编号:1009-3044(2007)04-11058-02
1 引言
在敏捷开发中单元测试是不可或缺的环节,单元测试除了保证了代码的质量外,在测试驱动开发中,测试更被改变地位,成为驱动开发主导的地位,每个功能的增加都必须写响应的单元测试以明确功能增加的目的性,形成的“测试-编码-重整”开发循环是敏捷开发的重要内容。
众所周知,撰写文档是软件开发的重要部分。在测试驱动开发,程序员在单元测试中留下了大量的功能说明和功能使用代码,这些正是软件功能说明文档的素材,在python中利用doctest框架写出的单元测试更符合说明文档的需要,所以结合doctest和文档系统(epydoc)我们否找到一种方法:利用单位测试的注释和代码直接形成规格化的
功能说明文档。
2 doctest
2.1 doctest介绍
在Python中doctest就是把程序中的docstring作为范例来运行的一个框架。
docstring可以由普通部分和执行部分组成,普通部分等同于原来的文字说明,执行部分由'>>>'(python shell提示符)或'...'提示符区分。程序员可以通过手工编写可预测的执行部分,也可以通过拷贝复制python交互式shell中的输入和输出简单的形成执行部分,doctest搜寻存在与各个模块,类和函数中的docstring,把每个可执行部分当成一次范例运行,再把实际运行值和期望值的对照作为一次运行结果。
2.2 doctest:单元测试的新框架
doctest常常用来完成三方面任务:保持文档与代码的同步;实现回归测试;可执行文档。这样就给测试框架多的Python又增加了一个不错的选择,对比其他测试框架(如unittest;py.test等)doctest具备了如下一些特点:
(1)无需安装,从python 2.1版本开始被纳入标准库;
(2)没有调用接口,使用方便,只需复制粘贴python shell中交互输入输出;
(3)通过命令行参数可灵活控制测试的执行;
(4)能很好地保持文档和代码的同步;
(5)测试用例也独立存在与代码分离的文件中,它们可以包含特殊的边界测试用例等;
(6)利用增强选项可增强输出文本匹配能力,比如ellipses选项,使地址,浮点数等可变输出得到正确匹配;
(7)利用_test_变量,灵活选择测试用的docstring。
利用doctest实现单元测试时,程序员独立创建只包含docstring的无函数体的测试函数实现测试单元,然后在docstring中编写测试说明和测试代码,它们是包含被测试目标的一些运行范例和期望的结果,利用doctest运行这些测试单元。
我们约定在一个被测试模块中包含的所有测试单元列表称为:testlist;而模块中被测试目标所有测试单元的列表称为testmap。
3 方法:结合单元测试doctest和epydoc开发敏捷文档
epydoc是Python中一个功能强大的文档系统,结合类似epydoc这样的文档系统,使基于doctest的单元测试能够产生和代码同步的最新文档,这种文档不仅展示了代码中包含的所多个模块、类、方法、函数、变量,更重要的是,它还能提供许多程序功能的“动态的”使用范例。
3.1 方法步骤
我们下面将模拟doctest的单元测试过程,在docstring加入epydoc链接,最后用epydoc输出文档。
(1)我们准备了要进行单元测试的模块P,包含一个类C;
(2)我们开始给模块P中的C.M1方法写单元测试。
方法是:在Python的交互窗口中运行C.M1的测试代码,然后复制&粘贴刚才的输入和输出到一个新的Python模块文件中(testlist_P),命名单元测试函数为test_M1,例子如下:
def test_M1():
"""
对M1测试行为的简短描述
被测试目标:
- L{P.C.M1}
testlist_P模块的main部分如下:
if _name_ == "_main_":
import doctest
doctest.testmod()
这是doctest在单元测试中典型的运行方式。实际运行测试时,我们只需在命令行方式下输入“python testfile_P.py”。
(3)现在,我们给方法C.M1补充初步的实现代码。在M1的docstring中,我们还增加一个Test Map的链接:
def M1(self):
"""
M1的描述
Test map:
- L{testmap_P.testmap_C_M1}
"""
(4)利用程序自动生成我们需要的模块P的Test Map,命名为testmap_P.py,其中C.M1的Test map内容(即所有测试到C.M1的测试单元列表)如下:
def testmap_C_M1():
"""
Testmap for L{P.C.M1}:
- L{testlist_P.test_M1}
"""
(5)调用epydoc输出文档:
epydoc -o P_docs P.py testlist_P.py testmap_P.py
输出的文档在P_docs目录下,由于都是HTML格式文件,我们只需把它们移到web服务器文档目录下来就能发布出在线文档。
当我们点击testlist_P模块链接,我们会看到模块P的testlist:
Module P_docs.testlist_P
当我们点击方法C.M1的docstring中的testmap链接,我们可以看到C.M1的testmap:
Testmap for P.C.M1:
* testlist_P.test_M1
为C.M2重复步骤2-5
(6)假设我们在单元测试方法M2时,同时也对M1测试,那么M2的测试函数test_M2可能是:
def test_M2():
"""
对M2测试行为的简短描述
被测试目标
- L{P.C.M1}
- L{P.C.M2}
>>> from P import C
>>> c = C()
>>> rc = c.M1()
>>> print rc
True
>>> rc = c.M2()
>>> print rc
True
"""
在“被测试目标”中,我们把两个函数都列出来了
(7)在方法M2的docstring中添加testmap链接:
def M2(self):
"""
M2的描述
Test map:
- L{testmap_P.testmap_C_M2}
"""
(8)利用程序重新生成模块的testmap,结果保存在testmap_P中,现在,C.M1的testmap将包含两个函数:test_M1,test_M2,而C.M2的testmap则包含test_M2:
def testmap_C_M1():
"""
Testmap for L{P.C.M1}:
- L{testlist_P.test_M1}
- L{testlist_P.test_M2}
"""
def testmap_C_M2():
"""
Testmap for L{P.C.M2}:
- L{testlist_P.test_M2}
"""
(9)运行epydoc输出文档:
epydoc -o P_docs P.py testlist_P.py testmap_P.py
我们点击testlist_P链接将看到:
Module P_docs.testlist_P
点击方法C.M1的docstring中的testmap链接:
testmap_C_M1()
Testmap for P.C.M1:
* testlist_P.test_M1
* testlist_P.test_M2
(10)重复步骤2-5把每个单元测试添加到testlist_P模块中
4 结束语
我们可以发现doctest、epydoc的结合使得产生功能说明文档变得非常方便,同时也非常强大。这里敏捷文档或许还被称作"literate testing"被看的见的测试或"executable documentation"可执行的文档,但不管如何称呼,名字看来是不重要的,重要的是我们得到一种方法:我们通过单元测试docstring中的代码的方式,文档化模块的功能,没有比这更敏捷的方法了。
参考文献:
[1]Robert C·Martin. 敏捷软件开发——原则、模式与实践[M]. 清华大学出版社,2006.
[2]Python Library Reference. http://docs.python.org/lib/module-doctest.html.
[3]Epydoc Homepage. http://epydoc.sourceforge.net/.
本文中所涉及到的图表、注解、公式等内容请以PDF格式阅读原文。