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

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

Oracle“炮轰”开源:称其永远无法在军事领域取得一席之地 零客户端:下一代计算分布模型 苹果聘奢侈品公司巴宝莉CEO Angela Ahrendts为零售主管 看看你会几个?9大最火的移动开发技能 为了进一步拴住用户:传亚马逊与HTC合作开发智能手机 三年?又三年!今年MDCC2013大不相同! 杨远骋:聚美优品与百度云推送的故事 糗事百科王坚:百度云推送让我们省心 10个最热JavaScript开源框架的社区活跃度对比 2013福布斯中国富豪榜:李彦宏第三、马化腾第五、马云第八 Storm又获东风,Hortonworks计划将其整合到Hadoop企业版 IBM CEO:预测高新技术 五个我最喜欢的方式 我伙呆,硅谷高级工程师年薪16.5万美金起 羡煞旁人的Supercell:Clash of Clans卫冕营收榜冠军 谷歌第三财季利润增36.5% 但摩托罗拉业务继续亏损2.48亿 针对应用开发者的几点建议:注意特征蔓延、加大用户评审…… Ubuntu中文衍生版UbuntuKylin 13.10正式发布 Web设计师和Web开发者之间的区别是什么? 在政府退出后,Google的量子计算机项目面临危机 SQL on Hadoop的最新进展及7项相关技术分享 联想:欲收购黑莓,还能在智能手机领域创造PC神话吗? 谁说Android开发者的收入依然不行? 代码可维护性的神秘面纱 MDCC 2013首批演讲嘉宾名单出炉 议题即将披露 MDCC2013:公开征集讲师和演讲主题 移动周报:IT人必看!9大最火移动开发技能 OpenStack H版发布 盘点2013:21个最火的云初创公司 从摩尔定律到原子计算,2013诺贝尔物理学奖背后的计算机技术 黄峻:从自制纸板键盘的孩子到Adobe技术经理的故事 Web开发正被颠覆 开发者需认清五大新现实 请问Java TM Programming Language中的TM(在Java的右角上)是什么意思啊? 这样的程序界面应该如何做?紧急求助!!! HelloImpl err: Cannot connect to ORB 是怎么回事呢? Source Insight3.1使用问题?正确解答者150分 哪儿有《The art of computer programming》下载? C++編程思想 各位情场高人,帮小弟一把,怎么把这个MM约出来,出个主意,俺先谢谢大家了!!! 我需要JAVA-JDBC打开数据库的例子.................... 谁有Delphi编译错误信息对照表,有中文注解的最好---50分 请问在SQLSERVER2000中如何在检索数据是将两个字段合并成一个字段,用“||”吗? 数据库的重定向问题! 关于使用资源 QRRichText 打印如果页数很多(2页以上),就打印不完全,但打印预览中可以看到全部,为什么? 我有一个弱弱问题要问。。。 在C#中如何做ToolWindow属性的WinForms?????????? 怎样才能将静态文本框的背景色设置为透明的? 实时错误“480”,不能创建AUTOREDRAW图象,如何解决?把PICTUREBOX定义太大会产生这个错误,不能绘图! COOL!COOL! 序列的一系列问题 劳烦各位介绍一下业界最流行实用的汇编,C语言编译器,我是在校生急需指导! Rebar 移动的问题!烦死我了! 高手留步:请问VC中获取当天日期的函数是???还有比较两个日期所差天数用什么函数??? 请问怎样才能将vb中的date型的内容转化为sql server的datetime型呀,谢谢大家了 请问,怎样在 FLAH5 中直接插入 MP3,并改变 MP3 的音质? 利用delphi连接远程sybase数据库,在多用户操作情况下为什么会产生数据库死锁 看了一下SL275(2001年5月)的课程概述,发现里面没有了Applet了,SCJP是不是舍弃了它了? 有关JAVA的Package,ClassPath的讨论,诸位看过来 COOL!COOL!COOL! what's wrong with the SOCKET define? 使用vb通过NotesSQL for odbc连接过domino数据库时总出现如下错误“ [Microsoft][ODBC 驱动程序管理器] 驱动程序的 SQLAllocHandle on SQL_HANDLE_ENV 失败”,有谁遇到过吗?如何解决? 在SDK中,如何判断对话框中的CheckBox是否选中? 请教如何修改如何提交数据(一对多的表) 如何在delphi里实现文件(比如文本文件)从一台机器传递到另一台机器?比较急! 不知道主板集成声卡的型号,又没有驱动程序?怎么办?主板是ASUS CUSL2,I815E CHIPSET 想问问大家pb自带的例子中有没有关于权限的例子 怎么样吧jpg图片转成ico?????? 听说程序员结婚生孩子多数会是女孩,是不是真的?,好像与辐射有关哦 那里有windoes版的ftp源码 不知道主板集成声卡的型号,又没有驱动程序?怎么办? 主板是ASUS CUSL2 ,I815E CHIPSET 请问哪里有编写多线程安全对象的资料?(72分) 通过界面输入大量数据,界面如何设计??? 在Red hat 7.1如何安装GBK字符集! Focus(老鱼)回答得50分 请教网站的建设计划如何写呀? 请问下面的DELPHI程序如何转为CB 帮忙测试! 怎样得到硬盘序列号 关于Web开发…… Notes Client 待办事宜的定时刷新问题 都是面向对象,怎么C++和JAVA对此代码的结果不同?欢迎高手讨论! 关于//{{AFX_??? 的问题 ---> 已知x+y=3,xy=1,a+b=5,ab=3,且m=ax+by,n=bx+ay 求m^3+n^3的值 根号-a的3次方除以a.a小于0.怎么化简. 等待他,突然累了.英文怎么写? 已知x+y=5,xy=2.a+b=3,ab=3,m=ax+by,n=ay+bx,求n/m+m/n的值 英语急转弯1.What kinf of mouse doesn't eat rice?( ) 已知方程组2x+5y{2x+y=-2① ax-by=-4和方程组{3x-y=12①,bx+ay=-8的解相同,求(2a+b^2013的值 2进制如何转化为10进制,请用一个简单明了的办法说明下,本人很愚笨,一般的理解不了, 急转弯 英语怎么说 英语翻译1.Without you how do I fly?You are my wings.Without you how do I happy?you are my heaven.2.My heart is a secret,waiting for you to understand the secret这2句话,别给我用其他的翻译工具翻译,我要人为的翻译.我都用过 2进制转10进制 11012进制1101 转10进制 一个英语急转弯,Two teachers teach at the same school.One is the mother of the other's son.what relation are they to each other 已知方程组2X+5y=-6 ax-by=-4和方程组3X-5Y=16 BX=AY=-8 的解相同已知方程组2X+5y=-6ax-by=-4和方程组3X-5Y=16BX=AY=-8的解相同求(2a+b)2007的值 若I3a+b+5l+l2a-2b-2l=0则2a的平方-3b=多少如果方程组x+Y+2ax-y=4a的解 2进制11111111转10进制 如果A=2除以根号5-根号2,B=3除以根号5+根号2,求(2A分之一)的2002次方乘以(B分之一)的2003次方的直 我害怕你打我 翻译成英文 最新的英语六级高频词汇和词组(发给我一份吧, a²-2a+1分之a²-1除以a的三次方-a²分之a+1,其中a=根号3 已知关于x、y的方程组2x+5y=-6和ax-by=-4和方程组3x-5y=16和bx=ay=-8的解相同,求(2a+b求(2a+b)的2010次方的值。 十进制转二进制的公式 ax-bx-ay+by 已知方程组2X+5y=-6,ax-by=-4和方程组3x-5y=16,bx+ay=-8的解相同,求(a+b)的平方的值 二进制与十进制转换公式? 十进制数127转换为十六进制数是多少 高中生英语词汇书 诗词 散文书求适合高中生看的英语词汇书,还有好点儿的语法书.同求诗词书籍,和散文集.最好是宋词多的书,像纳兰和李煜、辛弃疾那样的.谢谢谢O(∩_∩)O谢谢 2进制转10进制最简便方法?(公式) (log4(3)+log8(3))*(log3(2)+log8(2))-log1/2(^4√32)=?.. 若log3^5 log5^7 log7^x=log4^2 log6^4 log8^6,求x=? 有一个数是123,它与十六进制53相等的数值是多少?我很着急,计算过程也要, 10进制转16进制公式,这种结果对吗三、十进制数十六进制 如:76521转为十六进制 16|76521 4726 ――5 第一位(个位) 295 ――6 第二位 18 ――6 第三位 1 ―― 2 第四位 最后得1276516 二进制与十六 计算(log4 3+ log8 3)(log3 2+ log9 2)-- log1/2 32^(1/3)+5^(log259) (ax+by)^2-(ay+bx)^2 (ax+by)^2+(bx-ay)^2 log3 4×log4 8×log8 a=log根号3 9,则a= 小白加小白等于? 几道英语脑经急转弯1.When will a net hold water?2.What letter is a question?3.How can you make a repe shorter without cating or winding out it?4.who works only one day in a year but never gets find?并且翻译出来问题的意思.我一定 (log4 3+log8 3)(log3 2+log9 2)-log1/2 32^1/3+5^(log25 9) 通过估算,比较根号2分之根号5-1与8分之5的大小 英语脑经急转弯ben is very hungry.there is canned fish and canned chicken in the fridge.what does he open first? 英文翻译:我害怕一个人晚上外出怎么说? 小白加小白等于多少 问几道英语脑经急转弯!1.where can you always find mony when you look for it?2.how many sides does a cicle have ?3.where does the egg floating on the Mississippi River come from?4.why is the letter G and letter S in "gloves‘ close to each 你知道我一直是一个害怕孤单的孩子 英文翻译 八进制数0123的值相当于十进制的83;十六进制数0x123的值相当于十进制数的291.我不明白那83和291的结...八进制数0123的值相当于十进制的83;十六进制数0x123的值相当于十进制数的291.我不明白 英语脑经急转弯?有急用?1.where were you when lights went out?A.go to bed B.at the cinea C.in the sun D.in the dak2.what is the biggest in the world?A.the earth B.eyelid C.the sun D.the star3.what is easy to get into but hard to get out of?A. 若a,b,x,y均为正实数,且x+y=1,求证:ab≤(ax+by)(ay+bx)≤(a+b)^2/4 多元函数f(xy,x-y)=x²+y²,则af(x,y)/ax+af(x,y)/ay=? 十进制小数怎么转换为二进制小数要方法和0.5的例子!记住,是0.如果行, 将十六进制数321转换为对应的十进制数等于 化简(x²+xy+y²)(x²-xy+y²)=? 十进制小数如何转换为二进制十进制小数,如:0.325,如何转换为二进制,请写清详细过程和最后答案. 想提高英语词汇量,有什么推荐的词汇书吗? 比较根号2+根号7与根号3加根号五的大小 如何将带有小数的二进制数转换成十进制数,如0.011101?望各位大哥赐教,写出换算过程. 14的10进制转为2的跟8的进制 英语谜语,可能是脑经急转弯how many feet are there in a yard 十进制的小数怎么转换成二进制 设P=1/log2^11(底数为2的意思)+1/log3^11+1/log4^11+1/log5^11求p的取值范围A0 这是10进制转几进制的?6bb8121b3db2771a65daeacb29342c429ec1d948dc1841b1340d239b4b04d41a复原成十进制是什么? 十进制小数转换二进制的问题22.8125转二进制 “因为你,我很害怕”用英文怎么翻译?
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn