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

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

BlackBerry 10最终版SDK Gold发布 谷歌地图正式登陆App Store 为开发者推出SDK 你的云数据的三个最大威胁 公开叫板:Google不打算推出Win8版Gmail和Drive APP备案风波:开发者何去何从? 32位和64位的JVM 我该选择哪个呢? 黑莓“Got Game Port-a-Thon”活动将于12月22日在北京举行 专访Moti Joseph:浏览器安全的经验之谈 四大开源云平台的命运基因 FPS移动游戏:画质不是第一位,操控才是死穴 微软2013年的五个安全预测 应用成武器 王淮,大城小胖论辩HTML5 局部有小雨 技术人员创业后就不再适合继续编码了? Google Dremel vs Apache Hadoop 低功耗之争:英特尔 VS ARM NEIC-诺基亚体验创新中心 助力开发者创享新移动互联时代 Google地图主管Daniel Graf:新设计,新目标 浴火重生:OpenFeint创始人推开源平台OpenKit 紧随AWS数据仓库服务 Rackspace力鼎Cloudant 创业者谈:畏惧失败,但也要拥抱失败 谷歌赢得一场战斗 苹果能否赢得战争? 走进大学校园:是什么让学生成为独立开发者? 移动周报:为什么说独立开发者才是光明康途? MapFan eye:看日本地图导航应用的新玩法 数据会被清空:部分三星、魅族手机存安全漏洞 IE10简化文件访问 支持FileReader API 从MySQL迁移到MariaSQL Wikipedia力求开放 奥巴马筹款网站的制作过程 国内芯片厂商发力4G 新岸线2013年将推LTE方案 CMDN Club 23期:开放平台和O2O移动产品开发 花旗调低Apple股票评级 不信iPhone5会卖得好 全民健身 有谁使用过水晶报表? 求线段作另一线段的垂足 请问哪里有Delphi 6的控件下载? 怎么调用api画多边形? 求线段作另一线段的垂足 谁能帮我!错手把逻辑盘的东西删了,能不能恢复。 如何把一个文本文件的内容赋值给一个char* 大连高程何时报名? 需要这样功能的远程控制工具.... 谁知道广州搞php OR asp一月多少钱? 如何给JButton响应Enter键. W2K2000中的 SP 是干什么? 问题整理:求教关于Bezier的方程!!! 关于DJGPP和NASM 请问用什么样的方法可以令win98每隔两个小时死一次机或重启?谢谢 交论文的日子快到了--- 请问关于网站建设方面的,有哪些可写的? 50分,请问ms sql2000可以设置只显示用户表,隐藏系统表, JRE?JAVA 运行环境,指的是什么?要如何配呀? 关于记录指针问题? 50分,请问ms sql2000可以设置只显示用户表,隐藏系统表, 请问。。。 怎样在tomcat 401上面配置一个虚拟主机?(更详细的提问) 请各位能不能详细的解释一下客户端代码和服务端代码 activx控件屏蔽右键菜单 大家简单谈一下,现在学Delphi还有多少前途呢? 给大家带来一个好消息!! 如何关闭一个THREAD? 熟悉文件操作的高手看过来 求线段作另一线段的垂足 makefile中的库的路径指定? 各位老大!又出问题了......... 如果同时有两个或更多的钩子程序在运行,并且挂钩的消息是一样的,会是什么情况? 各位大虾,谁有用DELPHI制作的小精灵的控件. 我猜中国队以1:N(N>2)输给巴西队,听小道消息中国某财团从巴西买入一球。。。。 一个最难的难题 .net远程处理中的客户端激活问题 如何让CListCtrl中选中的行高亮显示? 选择一个文件夹怎么做???? 熟悉文件操作的高手看过来! 请大家帮我出出主意 电脑配置 帮帮忙! 2002年北京的高程报名了么。在那报啊。 VB与ORACLE数据库中的日期怎么转换 ? 菜鸟常问菜问题,菜鸟问题分虽少但是好赚 什么才是真正的程序员?! 一友跟男其友吵架了,竟说“看来男人都不是什么好东西”,详情请进 请问memo编辑框如何可以随窗口一起变大或变小 50分,请问在企业 管理器建立了a表,现在在查询分析器新增一个字段,怎样写 acess移植遇到的问题. 铁碗能放微波炉吗 甲乙两个圆柱体容器,底面积比是4:3,甲容器内水深7厘米,一容器水深3厘米.再往两个容器内各注入同样多的水,知道水深相同.这是水深多少厘米?要有具体过程. 浓硫酸,氧化钙为什么能做干燥剂,它们的原理是什么?好像不止是吸水性,还不能与什么反应?要求详细说明 酒精和84消毒液哪个的消毒能力强? 吃蔗糖或红糖能补血吗 浓硫酸具有吸水性 但不能干燥什么气体?同上 请问84消毒液和酒精之类的属于卫生材料吗?还是低值易耗品? 甲乙两个圆柱体容器,底面积比为4:3,甲容器水深7厘米,乙容器水深3厘米,再往两个容器各注入同样多的水,直到水深相等,这时水深多少厘米? 下列物质既能与金属钠反应放出气体,又能与纯碱作用放出气体的是( )A.CH3CH2OHB.H2OC.CH3COOHD.C6H12O6要有详细的解释哦~ 将一个体积为1*10-3立方米,重5.88牛的木球用细线系在底面积为200平方厘米的圆柱形容器的底部当容器中倒入足够的水使木球被浸没时,求:1.木球浸没在水中受到的浮力2.剪断细线,木球处于静 为什么用动物细胞制细胞膜比植物细胞容易? 下列物质中不能与金属钠发生化学反应的是 反应最剧烈的是水 稀盐酸 氢氧化钠溶液 氯化钠晶体 将26g某金属混合物投入到足量的稀硫酸中,共收集到2g氢气,该金属混合物的组成可能是( )A.Mg和Zn B.Fe和Zn C.Zn和Cu D.Cu和Fe 间歇式精馏塔的CAD图 你有没有阿? 给我一份吧要填料的 我的邮箱 348141241@qq.com 金属钠容易与空气中哪些物质反应?金属钠容易与空气中的什么物质反应,才被存放在煤油里? 底面积为0.03米平方的薄壁容器放在水平桌面上,自重2牛内装重有10牛的水面离地面高度为30厘米.求1.水对容器底面的压强.2.水对容器底面的压力 3.容器对水平桌面的压强 乙醇-水精馏塔课程设计,CAD,excel算法神马的,都要~(>_ 氢氧化铜溶于水吗? 将56g两种金属粉末的混合物与足量的稀硫酸反应,生成2g氢气,则这种混合物的可能组成是 ( )A.Mg,Zn B.Fe,Zn C.Mg,Fe D,Al,Zn cad图纸中主视图,剖面图哪个放上面啊? 氢氧化铜溶不溶于水? 圆柱的侧面积为31.4平方厘米,体积为15.7立方米,这个圆柱的底面积是多少平方厘米? 生物的细胞膜除了具有将细胞与外界环境分隔开﹑控制物质进出细胞﹑进行细胞间的信息交流这三个作用外,...生物的细胞膜除了具有将细胞与外界环境分隔开﹑控制物质进出细胞﹑进行细胞 氢氧化钠为什么会吸水?(详细一点)还有为什么它溶于水后,没有吸水性? 可能含有下列金属的镁合金15.2g与足量的稀硫酸反应产生0.4g氢气.则此镁合金中可能含有金属a.Al b.Fe c.Zn D.Cu 下列哪项不是细胞膜的功能?A将细胞与内环境分隔B控制物质进出细胞C进行细胞间信息交流D控制细胞新陈代谢 请问:易溶于水的无机化合物就一定具有吸水性吗? 假如2012年地球真的毁灭的话,现在大家最想做的是什么 硫化氢与氧气反应的方程式是什么?只有硫化氢与氧气反应 细胞膜的功能 进行细胞间的信息交流“进行细胞间的信息交流”太抽象了 交流什么?怎么交流?. 84消毒液和铁,VC有反应吗?刚用84拖了地,就冲铁粉和VC给宝宝吃,感觉颜色和白天的有点不一样,是不是和84超反应了呀? 怎样鉴别麦芽糖蔗糖蛋白质 细胞膜的功能:1.将细胞与外界环境分隔开 2.控制----------- 3.进行细胞间的------------- 第二第三是什 在84消毒液中加入铁 生成了黄色固体我把液体蒸发后 剩下的 黄色粉状物 请问这种物质是什么 蔗糖里有蛋白质吗?请问蔗糖里含有蛋白质吗? 细胞膜将细胞与外界环境分隔开作用不包括:将生命物质与外界环境分隔开.还是.使细胞内物质不能流失到细胞外,细胞外物质不能进入细胞内.理由! 烟屁股泡的水有毒吗吸完的烟屁股,泡到水里,那么毒性到底有多大呢? 蛋白质点样时为什么要加蔗糖 底面积为80cm2的圆柱形容器(重力不计,壁厚不计),放在水平桌面上,容器内装有重力为48N,深为60cm的水.将一个质量为800g,体积是200cm3的合金块全部没入水中后,水未溢出.(g=10N/kg,ρ水=1.0×103kg/m3)求 背部接近脖子处的皮肤上起了一些毒泡,里面好像有毒水怎么办? 蔗糖水补充蛋白质么? 红糖是什么做的?主要成份是? 现有镁铝铜合金6g,加入过量的盐酸中,在标准状况下放出氢气5.6L,反应后过滤得沉淀0.9g.若将此合金投入过量的烧碱溶液中,在标准状况下,能产生多少L氢气, 蔗糖溶液补充蛋白质了么? 白糖和红糖的主要成分是泔糖 利用活性炭的什么性,可将红糖脱变成白糖 石锦瓦长期泡在水池里,会影响水质吗,水会不会有毒 脂肪酸的合成脂肪酸的新合成.最后合成的为什么是 棕榈酸?这个酸对身体好吗?有什么作用啊 NH4NO3溶于水,苛性钠溶于盐酸,哪个是放热反应哪个是吸热反应 现有镁铝铜三种金属组成的混合物1.2g,将该混合物加入过量的稀盐酸中,在标准状况下产生的氢气1.21L反应过后滤得0.2g沉淀1;求该混合物中铝的质量分数2;若将等量的该混合物投入过量的烧 脂肪酸的生物合成脂肪酸的代谢是以β氧化为主,即每次“切下”一个二碳单位并与CoA结合生成乙酰CoA,那么脂肪酸在体内的合成过程是怎样的? 某同学想用氢氧化钠固体和盐酸反应来研究中和反应过程是放热还是吸热反应,请问他能得出正确结论吗?为什么?为什么?为什么? 大米在水里泡2天 在煮着吃有毒没? 脂肪酸只要用来合成什么物质打错字了,是主要用来合成什么物质补充下,是人体内的脂肪酸一楼:没记错的话,无氧呼吸是糖类分解成乳酸吧 在细胞内控制内外物质交换的结构是细胞膜吗?是不是? 现有镁铝铜金属混合物10g,加入过量的盐酸中,在标准状况下放出氢气8.96L,反应后滤得沉淀2.2g.若将此混合物放入过量的烧碱溶液中反应后,在标准状况下,大约能产生的氢气体积__L. 84消毒液加洁厕灵会产生氯气(Cl)这种有毒气体是八四消毒液和洁厕灵里面的什么元素组成才产品的氯? 谁有CAD精馏塔图 板式精馏塔的 有关氧气与硫化氢混合气体燃烧的体积计算常温常压下,将24ml的氧气和硫化氢的混合气体点火燃烧,充分冷却反应后到原来状态,测得有6ml二氧化硫生成.原混合气体中硫化氢的体积是()A 6ml B 蔗糖与红糖的作用有什么区别? 为什么浓硫酸有吸水性而稀硫酸没有
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘