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

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

SDCC 2013:阿里周宝方谈“去IOE”战略及实施 百度和高德:最后谁是地图的王者? 【CTO论道】瑞智和康CEO陶建辉:软硬结合是消费电子新潮流 10大高效率原型设计工具 JIRA与敏捷:李小龙教给我们的敏捷开发之道 百度开放云:Light Up The World,与开发者共享云未来 有胆你就来!11个在线编码大赛 CTO如何避免决策失控:第一步-定义角色 后机械硬盘时代,那些引领风骚的闪存初创公司 致远文杰:传统企业如何实现移动化转型? 记华为HCC2013 Day2:SDN开放编程系统OPS打造生态圈 HCC2013:华为FusionCube融合至简,性能至优 已确认Stephen Elop候选微软下任CEO 英语对于软件开发者来说到底有多重要? SDCC 2013中国软件开发者大会成功落幕 新浪微博宋琦:PHP在微博优化中的“大显身手” 8月份浏览器份额:IE浏览器PC端出现反弹 移动端跌出前五 如何让Ruby项目速度提升10倍 跨平台进行到底:Facebook发布Unity SDK 云薪资管理初创公司ZenPayroll:年处理薪资超1亿美元,获多位知名CEO力挺 Twitter开源Summingbird:近原生编码下整合批处理与流处理 亚马逊推Matchbook 欲将顽固纸质书读者拉向电子书 Broadcom为可穿戴设备推新开发平台WICED 再战江湖 New Kodak还牛不牛? 郭理靖:公、私有云架构异同点与难点 美化代码的15个代码语法高亮工具 你所不知道的虚拟化和云计算 [开源推荐]Effeckt.css项目:CSS交互动画应用集锦 针对Google Glass应用的四大UX设计原则及典型应用 Google收了摩托罗拉,微软收了诺记,黑莓又将花落谁家? 十亿月PV网站秘诀:Reddit自失败中总结的25条经验 程序是中文的,可以在日文的 WINDOWS 98 上正常显示 , 及运行吗 ? 有没有用过zope的大侠,谈谈感受吧:D LNK4089是什么原因引起的? ADO中,如何获得连接字符串? 请问关于OO的设计问题之二? 语句的错误? 换个id灌水的感觉真爽 一个最简单的存储过程 关于空间的问题 资源文件是什麽,怎麽使用? 请问怎样将下拉数据窗口中的某一行设置为当前所显示的行? InstallShield 如何做个 unistall 的 shortcut 到菜单里 scandir怎么用? 怎样从CMainFrame中调用DOCUMENT类中的PUBLIC成员!谢谢! 在线成中如何更新一个对话框 学习JAVA用JSDK开发还是VJ++?有什么区别吗?还是一样? "Resource id #1"是什么意思?? 主板上风扇都不转了,是什么原因??请教,谢谢!!! 乒乓球队进行比赛.... WIN98中公用文件夹窗体中文本框不能显示汉字 请问大虾:MDI窗体的菜单能不能和子窗体的菜单一起显示在菜单栏上??? 再谈关于PB控制Excel的问题。。。(ton2000) COM连接点客户端接受器的实现问题 为什么我的安装包不能安装?请高手指教,急啊。 送分问题:ASP字符串处理的函数有哪些? VB中将数据库中单条记录输出并打印! VB中如何宣告在DLL文件中的函数? 如何在正则表达式中加入变量 delphi 中的table 的filter有没有支持 like方法,该怎么用,同时filter当字段值为中文时怎么写? 主板上所有的风扇都不转了,硬盘和cpu都能正常工作,请教,谢谢!! 如何判别文件是不是文本文件? 新换了一块20G的硬盘,准备拿出10G安装Redhat7.2,请教最佳的分区方案? 关于武大华软 基于EJB的三层架构下的报表解决方案讨论...... 请问如何保存一个网站的资源 大学本科,,函授,自考,夜大,远程教育,自学!哪个硬! 比比! 斑竹到哪里去了???为什么我问的问题都没人解答的????斑竹职责何在??参与有分 基于EJB的三层架构下的报表解决方案讨论...... 哪位大侠可以给我解释一下“函数重载”是什么??请具体一些!! COM连接点客户端接受器的实现问题 用SQL SERVER中的imprort and export data 将paradox的数据库转为SQL SERVER数据库,可当数据库有密码时,如何转换? 请问java的xml解释器有哪些?怎么能在applet里用这些解释器? 一个小问题!有分哦 关于DBgrid的操作? 一个关于header的问题 一个小问题!有分哦 基于EJB的三层架构下的报表解决方案讨论...... 我这个LoadCursor为何不听话 jsp连接数据库(Access)的问题 局域网中为什么网上邻居里只能找到自己,别人找不到我的计算机,我也看不见别人的? LoadImageA()函数的参数的说明 我的世界 这是什么怪?在主世界的末地传送门附近有一个铁还是木门里看到的,还有很多怪 世界上最无聊的事情是什么 自来水功能和纯净水功能是什么意思?我按纯净水谁说我的水箱里的水是纯净水还是说把自来水过滤成纯净水? 如何同时检验碳酸根离子和氢氧根离子?rt对不起我没说明白 我想知道 他俩共存时 为什么一定要先检验 碳酸根离子? 利用海水得到淡水可采用的办法是A沉淀法 B过滤法 C蒸馏法 沉淀 过滤 吸附 蒸馏 这些方法净水程度净水程度从大到小排列 世界上有没有怪兽? 世界上第一大河是什么河? 偏铝酸根与酸的反应有水参加吗? 世界上存在怪兽吗? 偏铝酸根和氢氧根反应不? 偏铝酸根离子能和碱反应吗举例说明下 世界上什么东西最硬 世界上什么河最危险?为什么呢? 请问氢氧化铝加碱成偏铝酸根的反应原理讲得深点 中和反应是指酸碱反应吗?那酸和碱性氧化物的反应叫中和反应吗?有盐和水生成的反应一定是中和反应 这句话对吗? 某溶液只含以下离子中的五种,且各种离子物质的亮均为1mol 钠 镁 亚铁 铁 铝 氢氧根 碳酸根 氯离子 硝酸根硫酸根以下为条件1:若向溶液中加入硫氰化钾,溶液无明显变化2:若向原溶液中加 为什么水蒸气蒸馏温度永远低于100度 锂碘电池,电解质固体碘化锂,导电离子碘,为何锂做负极,碘作正极? 两性氧化物和酸和碱分别只生成一种盐和水吗因为定义只说既可以与酸反应生成盐和水又可以与碱反应生成盐和水的氧化物称为两性氧化物,还有一定要强酸、强碱才可以吗 世界上最大河流在哪里 细胞中的水具有运输作用,那它算是载体吗?高一生物必修一36页第二题求详解水在生物体的许多化学反应中充当:()A 溶剂 B 催化剂C 载体 D 还原剂为什么C不对? 能与酸和碱反应生成盐和水的氧化物 偏铝酸根可以看做弱酸根,那为什么不能和氢氧根反应? 老师,人教版高一生物必修2的89页练习基础题1(1)体细胞中含有两个染色体组的个体就是二倍体.看了您的解释有点迷惑! 盐酸和碱性氧化物反应的生成物是什么?A、碱和水 B.盐和水 C、金属和水 D、 酸和水 世界上什么河最大往印度哪儿想 电暖宝里面的液体是什么? 蒸发和蒸馏那一个主要是从溶解性的角度来考虑的? 关于偏铝酸根和镁离子.铁离子的方程式 电暖宝里的液体是什么东西?电暖宝知道是什么吧.就是那种用电加热的暖水带.我一不小心把里面的液体弄出来了,黄黄的液体,这个对人体有害吗?是什么东西 1.氢氧根离子 硫酸根离子 硝酸根离子 碳酸根离子 符号怎么演变,根据8电子稳定结构原理?2.为什么氢氧根离1.氢氧根离子 硫酸根离子 硝酸根离子 碳酸根离子 符号怎么演变,根据8电子稳定结 钠离子,偏铝酸根离子,碳酸根离子,氢氧根离子,向溶液中滴加盐酸,现象顺序是? 蒸馏需要用到什么, 氯酸根 ClO3- 硝酸根 NO3- 碳酸根CO32- 铵根 NH4+ 它们的离子符号是怎么确定的? 世界上最常的河流是那条? 实验室蒸馏操作中要用到的仪器 知道的有铁架台 铁圈 酒精灯 承接管 锥形瓶 再写出四种 世界上最硬最好的刀是什么 对于①静置、②吸附、③过滤、④蒸馏等净化水的操作,单一操作相对净化效果由高到低的是什么、、 蒸馏易燃液体可用什么蒸馏 实验室!蒸馏用的八个仪器是什么? 在水的净化中,静置、吸附、过滤、蒸馏各起到了哪些效果? 怎样证明铝化铝是共价化合物不好意思,是氯化铝 酸和碱发生中和反应生成盐和水例如H2SO4+CA(OH0)2 怎么快速记忆常见元素的化合价和原子团的化合价? 溶解操作中必须用到的玻璃仪器有什么和什么 世界上最幸运的事是什么 世界上什么东西跑的最快? 做过滤实验中缺少的玻璃仪器是什么.用到的玻璃仪器有什么 用一种溶液检验氯离子、碳酸根离子、氢氧根离子 世界上什么鸡跑的最快 在溶解,过滤,蒸发操作中,全都用到的玻璃仪器是什么 世界太多的事是靠幸运!很多天王巨星都是被星探发掘,然后就成了明星.同样的人别人累生累死三餐都成问题.为什么,他她有天份!为什么他她有天份别人没有,因为他她比别人幸运.成为明星他 世界上什么跑得最快 过滤用到的玻璃仪器 某溶液中含有大量氯离子,碳酸根离子,氢氧根离离子,如果只取一次该溶液就能检验全部离子下列循序正确的是1,+硝酸镁 2.过滤 3.加硝酸银 4.加硝酸钡 A.12423 B.42123 C.12324 D.42321 给出答案的请说 世界上什么跑的最快? 碳酸根、氯离子、氢氧根 怎样分别检验 世界上最神奇的事情是什么? 供水公司净水加氯时, 在氯钢瓶外壳淋水的作用是:
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘