说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

基于Python的测试驱动开发实战

HTML文档下载 WORD文档下载 PDF文档下载
顾名思义,TDD即进行编程时先把测试部分写好,当发现不能通过时,再进行编程以使测试通过。然后在这基础上适当地调整测试代码以实现更多功能,最后再编写代码使之实现。

【编者按】近年来测试驱动开发(TDD)受到越来越多的关注。这是一个持续改进的过程,能从一开始就形成规范,帮助提高代码质量。这是切实可行的而非天马行空的。

TDD的全过程是非常简单的。借助TDD,代码质量会得到提升,同时可以让你保持清晰的思路。TDD与敏捷开发可谓强强联合,特别是在进行结对编程的时候。本文主要介绍了TDD的核心概念,还有结合nosetest单元测试包进行Python示例简析。另外还会介绍一些Python备用包。

TDD是什么?

使用该方法可让你少走前人的弯路

顾名思义,TDD即进行编程时先把测试部分写好,当发现不能通过时,再进行编程以使测试通过。然后在这基础上适当地调整测试代码以实现更多功能,最后再编写代码使之实现。

TDD看起来非常像一个环,首先是要不断调整测试代码,然后是编码,改进,最后直至完成。先实现测试部分的做法会使你自然养成把问题放在首位的思维习惯。当真正去构建代码时,就不得不想清楚该如何把设计做好;比方说,该方法有何返回值?当遇到异常时该怎么办?诸如此类。

以这样的方式进行开发,意味着要想出不同的代码实现路径,并在测试中进行实践。这样做可使你少走前人的弯路:陷入一个问题后写出毫不相关的解决方案。


该过程可描述如下:

  • 写出一个缺陷单元测试
  • 使该单元测试通过
  • 重构

与敏捷开发结合

TDD与敏捷开发并行不悖甚至1+1远大于2,这里指的是代码质量而不是数量。

“这意味着结对双方都会参与其中,着重于当前工作,然后在每个环节进行互检。”

然而在结对编程时TDD是单独进行的。如果能把双方的开发流程混合好,互相都能理解就最好不过了。例如,其中一人写出单元测试,当测试通过后,另外一人可以编写不同的测试以之通过。

任何时候结对双方都可以互换角色,每半天或天。这意味着结对双方都会参与其中,每人都把精力放在当前任务上,然后在每个环节进行交叉互检。这难道不是一个双赢的做法吗?

TDD也可以是行为驱动开发过程中的组成部分,同样地,首先写出测试,只不过这里指的是接受测试。这样有助于把工作从头到尾都保持规范。

单元测试语法

进行单元测试时,使用到的Python方法如下:

  • assert: 编写个人声明的基本方式
  • assertEqual(a,b):检查a和b的是否等价
  • assertNotEqual(a,b):检查a和b的是否非等价
  • assertIn(a,b):检查是否存在b中
  • assertNotIn(a,b): 检查是否不存在b中
  • assertFalse(a):检查a的值是否为False
  • assertTrue(a):检查a的值是否为Ture
  • assertIsInstance(a,TYPE):检查a是否为“TYPE”类型
  • assertRaises(ERROR,a,args):以参数args调用a时,检查是否会出现ERROR

以上是实际当中使用频率最高的方法,更多的方法请查阅Python单元测试文档

安装并使用PythonNose

进行下面的练习前,请把nosetest测试运行包安装好。使用标准pip语句进行安装是最直接的做法。此外在项目中使用VirtualEnv(Python虚拟环境)也是不错的做法,因为它可确保所有包在不同项目中是独立的。假如对pip或VirtualEnv了解不多,不妨先查阅相关文档:VirtualEnv,PIP

pip语句十分简洁:

"pip install nose"
安装完成后,可以执行单个测试文件

$ nosetests example_unit_test.py

或者可以直接执行文件夹中的文件组

$ nosetests /path/to/tests
这里要注意的是每个测试方法都应以“test_”为开头,这样nosetest运行机才能正确识别出目标测试文件。

可选参数

下面介绍几个有用的命令行参数:

  • -v:输出更多信息,包括正在执行的测试文件名;
  • -s或-nocapture:进行PRINT语句输出,一般情况下这是隐藏的。开启后可方便调试;
  • --nologcapture:输出日志信息;
  • --rednose:一个可选插件,请点击这里下载,输出带颜色的输出信息;
  • --tags=TAGS:指定要执行的测试文件,而不是整个测试文件组。

实例分析和测试驱动方法

接下来结合一个简单的计算器类例子例如相加/相减,来讲述Python单元测试和TDD概念。对于add相加功能,会尝试编写一个缺陷测试。

在一个空白项目中,首先创建两个python包app和test。然后在每个文件里建立两个名为_init_.py空白文件。这是Phthon工程的标准结构,完成后可以拥有一个可导入的文件结构。如果需要了解更多有关文档架构的信息,请查阅Python包说明文档。 在测试目录里创建一个test_calulator.py文件,其代码如下:

import unittestclass TddInPythonExample(unittest.TestCase):    def test_calculator_add_method_returns_correct_result(self):        calc = Calculator()        result = calc.add(2,2)        self.assertEqual(4, result)

说明:

  • 首先,从Python标准库里导入标准的unittest模块
  • 接着,创建一个含有不同测试用例的类
  • 最后,创建以“test_”为开头的一个测试方法

完成后可着手编写测试代码了。执行方法前要先对计算器进行初始化,初始化完成后便可调用add方法,并把结果存入变量result中。完成后,使用unittest的assertEqual方法来确保add方法正常执行。

现在可以启动nosetest来执行测试文件了。代码如下:

if __name__ == '__main__':    unittest.main()

标准的Python文件执行方式为$ python test_calculator.py,相比之下本文使用的nosetests方法功能更丰富,例如可以运行目录中的全部测试文件。

$ nosetests test_calculator.pyE======================================================================ERROR: test_calculator_add_method_returns_correct_result (test.test_calculator.TddInPythonExample)----------------------------------------------------------------------Traceback (most recent call last):  File "/Users/user/PycharmProjects/tdd_in_python/test/test_calculator.py", line 6, in test_calculator_add_method_returns_correct_result    calc = Calculator()NameError: global name 'Calculator' is not defined ----------------------------------------------------------------------Ran 1 test in 0.001s FAILED (errors=1)
运行后可见出错的原因是没有导入Caculator。因为还没有创建呢!创建的方法是在app目录下建立calculator.py文件,然后导入:

class Calculator(object):    def add(self, x, y):        pass
import unittestfrom app.calculator import Calculator class TddInPythonExample(unittest.TestCase):    def test_calculator_add_method_returns_correct_result(self):        calc = Calculator()        result = calc.add(2,2)        self.assertEqual(4, result) if __name__ == '__main__':    unittest.main()

Caculator构建好之后,再次运行看会出现什么结果:

$ nosetests test_calculator.pyF======================================================================FAIL: test_calculator_add_method_returns_correct_result (test.test_calculator.TddInPythonExample)----------------------------------------------------------------------Traceback (most recent call last):  File "/Users/user/PycharmProjects/tdd_in_python/test/test_calculator.py", line 9, in test_calculator_add_method_returns_correct_result    self.assertEqual(4, result)AssertionError: 4 != None ----------------------------------------------------------------------Ran 1 test in 0.001s FAILED (failures=1)

很明显,add方法返回了错误的值,因为还没有为它指定行为。幸好nosetest会指出出错的位置,方便进行修改。稍作改动后,测试便可通过了:

class Calculator(object):    def add(self, x, y):        return x+y
$ nosetests test_calculator.py.----------------------------------------------------------------------Ran 1 test in 0.000s OK

虽然通过了,但是围绕该方法还可以做更多的工作。

沉迷于某个案例很容易造成短视

如果进行非数字型数据相加会导致什么后果呢?事实上Python是允许字符串或其它类型进行相加的,但在我们的例子里不允许。接着尝试就这个例子加入另一个缺陷测试,然后使用assertRaises方法来判断是否有异常抛出:

import unittestfrom app.calculator import Calculatorclass TddInPythonExample(unittest.TestCase):    def setUp(self):        self.calc = Calculator()    def test_calculator_add_method_returns_correct_result(self):        result = self.calc.add(2, 2)        self.assertEqual(4, result)    def test_calculator_returns_error_message_if_both_args_not_numbers(self):        self.assertRaises(ValueError, self.calc.add, 'two', 'three')if __name__ == '__main__':    unittest.main()

以上代码中,检查了是否引起了ValueError错误,其实还可以进行更多的检测,不过在这里不作深入讲述。此外,setup()方法用于推入计算对象。下面再看看nosetest会反馈什么信息:

$ nosetests test_calculator.py.F======================================================================FAIL: test_calculator_returns_error_message_if_both_args_not_numbers (test.test_calculator.TddInPythonExample)----------------------------------------------------------------------Traceback (most recent call last):  File "/Users/user/PycharmProjects/tdd_in_python/test/test_calculator.py", line 15, in test_calculator_returns_error_message_if_both_args_not_numbers    self.assertRaises(ValueError, self.calc.add, 'two', 'three')AssertionError: ValueError not raised ----------------------------------------------------------------------Ran 2 tests in 0.001s FAILED (failures=1)

显然nosetests告诉我们ValueError没有被抛出。现在我们有了一个新的缺陷测试,接着尝试编码进行解决:

class Calculator(object):    def add(self, x, y):        number_types = (int, long, float, complex)         if isinstance(x, number_types) and isinstance(y, number_types):            return x + y        else:            raise ValueError

代码中使用了isinstance方法是为了确保输入的是数字型数据。

由于两个变量的类型有多种组合,为了进行完整的测试,所以需要把可能出现的组合进行统筹并进行处理:

import unittestfrom app.calculator import Calculatorclass TddInPythonExample(unittest.TestCase):    def setUp(self):        self.calc = Calculator()     def test_calculator_add_method_returns_correct_result(self):        result = self.calc.add(2, 2)        self.assertEqual(4, result)     def test_calculator_returns_error_message_if_both_args_not_numbers(self):        self.assertRaises(ValueError, self.calc.add, 'two', 'three')     def test_calculator_returns_error_message_if_x_arg_not_number(self):        self.assertRaises(ValueError, self.calc.add, 'two', 3)     def test_calculator_returns_error_message_if_y_arg_not_number(self):        self.assertRaises(ValueError, self.calc.add, 2, 'three') if __name__ == '__main__':    unittest.main()

至此我们可以运行所有的测试了,所要实现的需求也都满足了。

其它的单元测试包

py.test

pytest的作用与nosetest类似,不过可以在单独的区域里输出信息,这意味着能够使我们很快地看清楚命令行中出现的打印信息。这对于只运行单个测试的情况是很有用的。

创业教父Paul Graham谈创业投资趋势 挖掘Linux人才,打造Power生态系统!IBM启动“天合应用服务器定义未来挑战赛” 请不要忽略API的安全性 发布将近?苹果在多地注册“iWatch”商标 苹果聘请某时尚公司CEO为副总裁 将负责“特殊项目” 雅虎5000万美元收购视频分享应用Qwiki 创业第一年易犯的8大错误 苹果推iOS游戏手柄,掌上游戏主机格局变天? 代码审查固然伟大,那还有其他替代品吗? 怎样维护一个成功的开源项目 麻省理工专家发布新系统Ascend,专注内存访问安全缺陷 Oracle Database 12c面临的最大挑战是开源+开放的生态系统?! 浸入式数据中心:高密度计算的新前沿 2013年腾讯合作伙伴大会要点回顾 “鼠标之父”和超文本研究先驱Douglas Engelbart去世 享年88岁 Web服务器排行:Nginx超越Apache 成为全球TOP 1000网站最常用的Web Server Windows 8之父Steven Sinofsky正式离职微软 不得接受竞争者聘用 Yelp处理1.02亿/月绝对造访人次及3900万评论的省钱秘诀 可视化:图像的主题色提取算法,是不是太高端了? 不利于写出好代码的15个职场因素 GPU Saturday技术沙龙:分享异构计算新技术及应用 微软隐藏最深的秘密武器之一——Visual Studio可作为Python IDE 历时六月,Qt 5.1正式版终于面世! HTML5全硬件加速游戏引擎:Sphinx正式公测 专访“Monster Blade”创始人:无需营销,百万下载背后的秘密 Twitter实时分析平台主要合作伙伴Dataminr:让用户不再错过重要的推文 OpenStack、OpenNebula、Eucalyptus、CloudStack社区活跃度比较 XOR的黄色大象:Erasure Code为Hadoop节省数据恢复带宽 竞争将越来越激烈:戴尔或也进军可穿戴计算市场 新技术:可直接在人脑播放广告 编程调试和诊断的五大规则 请问如何实现全文检索? 关于托盘编程!急!为什么我的程序隐常不了!!! 如何约束?再线。。。 急了几天了,大家进来看看! gcc 自写连接池连接的释放问题?(请高手指点!) 怎么在lotusScript中取到所有用户的名字? 我用jb创建一个web application的project,请问如何添加WEB-INF中的class 【醒目】多页报表问题!达人入!!!【醒目】 用php如何获得登陆者的ip地址?50分 纯java驱动连数据库,单是jsp能调用,但做成javabean就不能了!麻烦各位大哥帮忙 公司严重拖欠薪金,请问到劳动局追讨工资是否可行[up有分] 一个人,35岁与30岁有什么差别? myjdbc.jar 虽然别人谈过很多了,还是想再请教怎样学习vc 2003成都美女节 求助!有关文件(2) 关于刷新问题(定位)的,急用!! 试贴 下载问题,我发现一个奇怪的问题。中文名的文件的下载。。。 ADOQUERY无法更新数据集 帮我看看,怎么编译不通过 各位板主:折腾了我三天的问题还未解决。 delphi qreport byte& 是什么类型? IBM DDYS-TO9170M SCSI 硬盘,如何看它的CACHE,写者10000转的,不知道是不是? IBM DDYS-TO9170M SCSI 硬盘,如何看它的CACHE,写者10000转的,不知道是不是? 急!我在传输参数到SQL的过程中,服务器提示“AAA”需要参数但没有参数,请各位高手给个意见!在线等! 弹出窗口的问题--在线 串口工作有误了,MSComm32控件的问题?? 想知道有必要先学C 再C++ 再Visual C 吗? gqxs (小龙)请进,看到你去年的一个关于注册的帖子,我也出了像你一样的问题,请大家都来看看。 DBDateTime控件在哪里可以找到? 那为能够帮我解释一下SQLBindCol函数,特别是参数。 如何操作recordset,使其能够手动付值!即用 window nt和window 2000 server能不能象windows professional那样设成重启后自动登录。 关于语音视频聊天室 高手进入 来者得分 通过GUID枚举设备的问题请教! 代码不懂请指教 如何用javascript控制表单提交? (急急-在线等)一个复选框的问题 如何让服务器知道前来访问的客户机物理内存大小? 急,急,帮我看看。一个初级问题。 用思科1721路由器代理上网和连接网络电话功能够用吗? 请问用嵌入式系统开发的成本大约是多少? 如何获取两个日期之间相隔的天数? PB中如何操作文件。 在线等急盼tomcat启动问题 我的奔4电脑不能装oracle8。1。7 java 与oracle数值类型转换问题 请教“数字权利管理”(digital rights management)和P3P的实现问题? IBM DDYS-TO9170M SCSI 硬盘,如何看它的CACHE,写者10000转的, 第5个,连词成句 ☎☏应用题☎☏从0,1,2,4,5,7中,选出4个数,排列成能被2,3,5整除的4位数,其中最大的是_______.找出4个互不相同的自然数,使得对于其中任何两个数,它们的和总可以被它们的差整除,如果 连词成句,3、5 为什么,在趋势中连接两个同级别的"缠中说禅走势中枢"的必然是次级别以下级别的走势?1.而不能是同级别走势?2.缠中说禅的级别是什么概念? 请问这是什么杂草?哪种农药可以杀死它?叶片和水稻相似,无叶舌叶耳!根系比水稻粗状! 多年生的杂草 什么农药最好用 民主制度体系主要包括 秋雨有什么特点啊老师要我们写的我根本不了解秋雨希望你们告诉我秋雨有什么特点要仔细点的 关于秋雨特点 你的爷爷奶奶,今天下午打算做什么用英语怎么说? 5个,连词成句! 《三国演义》连环画法文版在法国上架马凯会见比利时首相迪吕波沃尔玛3年欲设新址110个查尔斯不想当国王? 英媒爆王储担心被《中国人看白俄罗斯》首发 获赞民间外希腊疑遭拐卖女童证实身份 其母曾企图英国王储查尔斯否认说过“当国王像蹲监韩调查显示 面试官平均14分钟决定是日媒称4架中国军机同一天越过冲绳飞向专家:旅新大熊猫嘉嘉可能已进入性成熟揭秘网络相约自杀群体:低龄化 渴望得“与时俱进,音乐才不会腐朽”A股助众多中国导演暴富中石油华润等19家企业因环保问题被罚文化与政治的互动: 冷战时期两岸的文南京民间艺术家联合会成立古诗中的美丽初夏“上海应该抓一个经济变革的大作品”丽珠集团董秘主动辞职青奥倒计时唐慧女儿案被告死刑未核准10大明星楼盘角逐楼市世界杯剑道魔心2012魔界重开圆梦篮球国度剑谕紫气炼体五行幻化鸿天堑玄兵鉴苍龙风云修神纪噬界无棣旅游玛纳斯旅游盈江旅游南丹旅游陈巴尔虎旅游苍溪旅游北镇旅游嘉禾旅游施秉旅游南安旅游富川古明城旅游
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘