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

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

BDTC 2014干货精彩不断,四大全天技术论坛议题全面曝光 全功能API!开源2D游戏引擎Firmament 微信公众平台新增“微信连Wi-Fi”功能,打通线上线下闭环 马化腾:微信在一线城市活跃 QQ增长从城市到农村 承包MIPS开发!Imagination发布双Codescape MIPS SDK 不任性了!Google Play宣布开通国内开发者通道 脑洞大开!用Swift写个Material Design组件库 美国最大婚恋网站eHarmony利用Hadoop、OpenStack重构自己 每个Java开发者都应该知道的5个JDK工具 异构计算应用研讨暨OpenCL编程培训在上海和西安成功举行! APIs.io:用来发布和发现API的利器 偏爱MySQL,Nifty使用4个Web Server支撑5400万个用户网站 直接拿来用!十大Material Design开源项目 初探WatchKit,开发者必须知道的6件事! 【MDCC专访】环信CEO刘俊彦:音视频即时通讯将成为技术壁垒 第十七届全国工程建设计算机应用大会在京召开,AMD受邀详解GPU Computing! 哪一门开发语言薪水最高? 借助ArangoDB,带你玩转Google图算法引擎Pregel 【最具价值CTO评选秀】MediaV CTO胡宁:技术女神的自我奋斗 豌豆荚邓草原:如何实现实时响应式平台 必须谨记!Node.js安全开发技巧 聚焦新平台新应用 IBM 2014技术峰会上海站顺利落幕 《近匠》NAGA娜迦信息:移动安全加固定制化 棱镜、飞流、TalkingData三方解读:手游如何高效运营 【MDCC秀场】枕戈待旦——秉持移动开发大潮下的主角思想 Netflix的首席产品官Neil Hun谈推荐系统打造和AWS实践 微信开放WiFi接口,醉翁之意在乎O2O还是大数据? 软硬整合:基于云的微型智能防丢设备Linquet 【MDCC专访】AppCan CTO赵庆华:助力企业快速移动化 使用Bluemix中的Rules服务构建一个酒店预订应用程序 基于IBM Bluemix开发微信公共账号应用 讨论一下,对于个人来讲计算机可以帮助我们做什么? 谁有WS-FTP pro 7.5的注册码! 怎样改变MENU的字体? 请问有谁愿意转让《Object-Oriented Software Engineering: A Use Case Driven Approach》一书 讨论一下,对于个人来讲计算机可以帮助我们做什么? 谁用C++ BUilder写过类似sql 语句生成器的东西。 有谁知道 NetBoy 3.0.1 的序列号?第一个答对者20分全给!本人说话绝对算数。 ADO,如何判断一个连接对象(_ConnectionPtr)是否已经打开? IIS 5.0 & exchange server 有沖突? 修改数据库问题,急,在线等待! 讨论一下,对于个人来讲计算机可以帮助我们做什么? 怎样实现c语言对数据库的操作?(查询,添加,删除,排序。。。) 建立ActiveX控件的问题 求救:java的‘主要类型’本质上是不是对象? 如何编程实现改变显示器的分辩率? 有谁知道 NetBoy 3.0.1 的序列号?第一个答对者20分全给!本人说话绝对算数。 请大家给个方案:关于中文词法分析? 请 newly_ignorant(不学无术) 兄进来领分 怎样控SourceSafe的物理权限 ? 向各位高手求助!简单goto转变成没有goto的语句· 怎么改变DataGrid页导航条数字颜色 谁有IPX在局域网里收发和解析IPX包的例子! 释放连接,在线等待 我想将一个表中的所有字段的内容送入word中规定的表格中,请问谁能给出一个例子??? 如何删除folder.htt,和desktop.ini文件? ADO问题 为什么用 filelistbox 写完的程序换到其他的机器上就不好用了? JB7编译的exe文件为什么不能运行 vb调用存储过程(mssql,oracle),是否一样?不一样怎样做到一致?! 学习ASP STL问题2,怎样获得元素在list中的位置(序号)呢?? 法国人为什么不喜欢说英语 热烈祝贺 张一飞(intfree)、侯启明、俞玮 夺得 IOI 2002 金牌! 我在2000下写的程序在2000下能正常使用,但在98下出现错误!为什么? 怎样实现c语言对数据库的操作?(查询,添加,删除,排序。。。) Listctrl问题 dim objconn as ADOconnection 提示出错 哪位朋友有SnagIt(V5.2.1)的注册码? think in java里一段关于snake.java的程序没看懂,请大家给解答一下,谢谢 一个常用的简单问题?请帮忙。(本人刚从asp转过来) 为什么程序突然跳到00000000? 有谁知道 NetBoy 3.0.1 的序列号?第一个答对者20分全给!本人说话绝对算数。 谁有这样的算法:给定两个区域,用直线或折线来连接,以及移动其中线段的算法。 UDP高手:建立了一个服务器后,如果。。。 用ISAPI编程怎么实现文件的传输(象网络硬盘那样的功能) OpenGL与C++Builder 请问在ORACLE中如何列出所有用户创建的表存储过程等? 急!在网页中如何能使汉字旋转90度,急用!!! 数据库中按某个字段排序的问题 这何种算法?(排序) 哪里有POSIX标准下载(免费的)? 三峡大坝刷新了哪些世界之最? 光补偿点时叶绿体中ATP的去向?线粒体中二氧化碳的去向? 2a-3b^2=-1,2a+3b^2=5,则9b^4-4a^2=?6还-6 若a为有理数,试比较a与1/2a的大小 有丝分裂,染色体变为2条染色单体是在前期还是间期? 10的2x次幂=25,则x= 求哲学中 旧唯物主义的反映论和旧唯物主义认识论 的概念 怎么区别有丝分裂的分裂间期和分裂期染色体、染色单体和DNA数目 使用delphi编程,输入一个年份,判断是否为闰年?急用,1.能被4整除,但是不能被100整除.2.即能被100整除,又能被400整除. 旧唯物主义认识论与辩证唯物主义认识论的区别在于是否坚持反映论 有丝分裂间期染色质变为染色体就会有染色单体吗? 请问,环保标准国3和国4区别? 唯物主义认识论,唯物主义反映论,唯物主义辩证论的区别 人类的一个细胞处于有丝分裂的分裂间期,前期,中期,后期和末期时,细胞的染色体,DNA,染色单体数量变化 国四标准讨论系列 国四车能用国三油吗 3.如何理解辩证唯物主义认识论是能动的反映论?它和唯心主义认识论、旧唯物主义认识论有什么区别? 一个正方形被分成5个相等的长方形,每个长方形的周长都是60厘米,这个正方形的面积是多少平方厘米? 爱国有什么意义为啥要搞统.一,人家想分出去,不想跟你一家,那是别人的自由,为何要强迫别人意志,达到所谓“大统.一”的局面.是“统.一”了地盘大了,能多捞.钱吗,这于我们普通百.姓有何益 辩证唯物主义认识论把实践引入认识论,把辩证法贯彻于反映论.正确 错误 反比例函数Y=(M-1) 1/X^M2-3的图象在2.4象限,则M=? 爱国的定义是什么 国家规定的住户用电每平方多少瓦 叶绿体,线粒体都能合成ATP?都能利用ATP?叶绿体合成的ATP能否供线粒体使用?线粒体合成的ATP能否供叶绿体使用? 两相电怎么变三相电?需要用到三相电3000瓦的抽水机,只有220伏电源,没有三相电听说可以报装两户220伏的电,然后自己弄, 每年平均用电多少瓦全世界每年平均用电多少瓦 ? 细胞有三个能合成ATp的地方·.除了叶绿体线粒体还有什么地方? "爱国" 的具体含义 笔记本一般用电是多少瓦 事件查看器中的数字例如:6005、7035是什么意?事件查看器---系统---gk\事件查看器----系统----事件中的数字 家用三相电变两相电接法怎么接A、B、C、N四条线来,三相电就是指A、B、C三相,其线电压为380V;而单相电就是从A、B、C三相中任接一相,再从N接出一相就组成了单相电路,AN、BN、CN的电压是220V. 家庭用电220伏等于多少瓦? 事件查看器中的数字例如:6005、7035是什么意思? 负数分奇数偶数吗?零是不是非奇非偶? 在下列实验中,必须始终用活细胞做材料的是 A.用光学显微镜观察植物细胞有丝分裂 B.用光学显微镜观在下列实验中,必须始终用活细胞做材料的是A.用光学显微镜观察植物细胞有丝分裂B 家庭用电三相电的电价格是多少 谁能帮我起个名,要有符号和数字,还要有空格,不要汉字, 借助光学显微镜,可以详细观察活细胞有丝分裂全过程 三相电以及家庭用电最近单位三项电搞得我很糊涂,每一项都是380V,接个零线就可以接电器.那么以前都说家庭用电是220V(一根零线一根火线),这个家庭用的火线是不是380V?我以前老认为是220V的. 汽车的排放国4与欧6哪个好 do you like _____a little longer in the morning.为什么用to sleep而不是sleeping 如图,在半径为R的半圆里,AB为直径C,D是半圆的三等分点,求图中阴影部分面积 从三相电中取两相来家庭供电可以吗两根火线之间的电压是220V,但两相都是火线,用在照明电路中是否有安全隐患? 英语考试 I like to s———— a little longer in the morning.1.I like to s———— a little longer in the morning.2.We eat —— supper late in the evening.A.a B.an C.one D./3.翻译句子我观看电视的早间新闻.I watch the early _ 如图,C、D是以AB为直径的半圆上的三等分点,半径为R,求图中阴影部分的面积 (要具体过程) 最小贴片电容有多大 边长是8厘米的大正方形,剪成4个相等的长方形,长方形的周长是多少 同底数幂的乘法与同底数幂的除法区别联系 4y²-4y/y²-2y+1÷(y+1/y-1-y-1/y+1)÷(3y+3) 四个一样的长方形和一个小正方形拼成了一个大正方形,大正方形的边长是5厘米,小正方形的周长是8厘米,那么长方形的宽是多少厘米?六年级的题可以把算式写的完整一些,这样看起来就比较懂 线粒体和叶绿体产生ATP和水的部位的区别 (a+1)(a-1)(a²+1) (2y-1)(4y²+1)(2y+1) 参加社会实践活动有什么好处?你有什么看法? 七年级下册的第一章整式的运算1整式2整式加减3同底数幂的乘法4幂的乘方与积的乘方5同底数幂的除法6整式的乘法7平方差公式8完全平方公式9整式的除法和1花边有多宽2配方法3公式法4分解因 单相电零线断了用电设备会怎么样,三相电零线断了用电设备又会怎么样? 怎样在电脑上打出根号,幂的符号如2的平方,(2^2除外)和二次根号2计算机高手赐教 叶绿体中有线粒体吗,它自身不是可以产生ATP吗 在串联电路中,拧下一只灯泡,其余灯泡还发不发光?有时两灯都不亮,把导线或灯泡动一动就都亮了,这说明发生了什么情况?电路中有没有电流? 一:正三棱柱底面边长是3cm,侧棱长都是5cm,则此三棱柱侧面展开图的面积是________平方厘米.二:一个八棱柱的底面边长是3cm,侧棱长之和为56cm求这个八棱柱的侧面积.三:两个完全相同的长方 有丝分裂,一开始间期是不是只有染色单体?染色单体是单一条吗?染色体是两条染色单体相交吗? ..救命22.有一个负载电阻值为R,当将它接在20V的直流电源上时,消耗的电功率为P,若将R接在图2-8中的变压器的次级电路中消耗的电功率是P/2.已知变压器的输入电压的最大值为200V,求此变压器的
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘