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

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

走进VB6啃一块骨头 做成一个时间限制的测试版 -VB资料 PHP中利用jQuery实现SWF图像上传和裁剪 VB.NET或C#将DataSet快速导出到Excel中 HashSet的equals和hashCode重写问题 PendingIntent实现原理和代码 IntentService实现原理及内部代码 AsyncTask实现原理和内部代码 Neither user nor current process错误 ListView的右边滚动滑块启用方法 Live Wallpaper动态壁纸开发 控制软键盘显示和隐藏 设置Activity大小不再全屏原理 Android面试题,看看你基础如何? Android Canvas绘图抗锯齿解决方法 Android xliff和字符串资源 android.hardware.USB类介绍 Android开发经验谈之应用间数据共享 android_asset这个URL详细使用方法 Android 2.3系统自带高清UI图标下载 设置ScrollView滚动条的颜色 WakeLock使用方法示例代码 Concurrent并发库常见问题总结 Android数据库SQLite性能优化技巧 Android开发之Java设计模式基础篇 Android开发之Java设计模式入门篇 Android开发之Java设计模式 Android开发之Java并发包集合类性能分析 Android开发之Java集合类性能分析 Android开发之Java虚拟机原理和内存分配 Android开发之Java基础系列教程目录 收购C、C++、JAVA版完整工程源序 VC中如何实现等待5微秒? 关于如何用代码描述rc文件, 那位用过触摸屏的,给个建议, 请问微秒级的延迟在C语言中如何实现? 请问为什么在COM中总是使用双重值针,即指针的指针? 用过,销售,触摸屏的兄弟姐妹给个建议 关于C里面的setjmp()和longjmp()函数的使用。 一个用VB偏的“图片批量转换软件”!带源代码!!! 多伦多穷人欢庆申奥失败 有人注意到了吗???directX的资源释放不干净! 谁能帮我做一个小型帐务管理(用C)??? 用delphi编程,用什么触摸屏比较好啊? 怎样在程序运行时增加子菜单(要增加的数目是未知的) 关于Sprite动画 出售代码 ACCESS的FORMS DLL调用函数问题 VB在ACCESS中应用的一个小问题! ACCESS中的FORMS问题? 再普通不过的一个问题 delphi6中还有BDE和ADO吗?它们被淘汰了吗?现在还有必要用它们来学数据库的开发吗? 50分答谢!!!---这个css定义为什么会出现这样的效果??? 各位:怎么判断表里某个字段是否存在? 纯技术问题:精通“书签“与“层“的大虾请进!!!! 一个奇怪的现象,请教高手这是怎么回事? 哪里有破解清华五行卫士密码的软件? vc++7.0好用吗? 吃完晚饭了... 可不可以让 CppWebBrowser1的背景不是白色? 热热热!!!热疯了!!! 我的喇叭哪儿去了? 我的PB程序出了问题了!!! sql server中哪个函数是用于字符串与整型之间转换的? 我想限定文本输入框只能输入10个汉字或20个英文字符,但文件输入框却只能输入10个英文字符,如何解决? 当我将这样的字符串“dsdsdsdfsdffsdsdfsdfsfdsdfsdfsfsfdsdffs.....”放入我的表格中时,无论怎样设置也不折行,影响了部局,如何解决? MFC中视图(view)是怎样随着主窗口的大小改变而改变的。 急!请教!!猫猫连接,用数据管道同时访问两库,访问不到对方的网络库(sybase) 关于电影下载 我已经把一个exe文件加入到我自己的程序中了,请问在我的程序中应该样调用该exe文件呢? 哪位高手知道RTF格式的含义?或者更好,知道Rxlib中的RXRICHEDIT控件中识别RTF格式的方法? Delphi编写的DLL在VC下不能使用 请问哪儿有电工的<>的电子版下载?我只有这么多了 msdn2001年4月版的讨论 请问哪儿有电工的<>的电子版下载? 请教高手!!!! 请进 各位:周末愉快!为何我的多表查询用updatebatch提交时总是报错? 我不明白!!!! 请问,BAS 与 VBJ 有什么区别吗?为何他们安装在同一个目录呢? 如何得到系统进程的列表? vi下如何撤销操作? 判断:36和48的公因数有2、3、4、6、12,其中最大公因数是12. 科学题 (20 21:0:21)          把5毫升质量分数为98%的浓硫酸(密度为1.84克/厘米3).加入到25毫升水里进行稀释,求所得溶液的溶质质量分数. 求初二物理试题 45和15是因数和倍数的有哪些 24.36.48 公因数 (26 15:54:30)一艘船在河中逆流而上,河水流速为3米/秒,经过一座桥时,船上的一只木箱掉落水中,5分钟后船员才发现,立即掉头追赶,同时将航速(船相对与静水的速度)提高到原来的1.5倍,则可在 请问72和48公因数有多少? 96和80的公因数有哪些 (26 15:39:30)甲、乙两物体都做匀速直线运动,速度之比是4:3,所用时间之比是5:2,则路程之比为 72的公因数是,90的公因数是,最大公因数是,最小公因数是 160的公因数 初二科学题(关于水的浮力的) (7 15:53:16)武汉号满载时重7.5*10^7N,求满载时军舰排开海水的体积. (  ρ海水=1.03*10^3kg/m^3  g=10N/kg      结果保留两位小数) 108和144的最小公因数 120和56的公因数是多少? 初二 物理 初二科学 请详细解答,谢谢! (5 15:45:30)铝球的体积为400立方厘米,质量为810克,问,该球是实心的还是空心的?(铝的密度:2.7×103千克/立方米) 用短除法求下面各组数的最大公因数和最小公因数4和6 18和20 72和96 81和108 77和160有没有公因数 科学 (24 20:53:14)下列几种用电器正常工作时,电流最大的是(  )A.42寸液晶电视机   B.MP3       C.电子手表            D.100瓦 93和108的公因数(这个因数的倍数不能有96、99、102、105)108的的因数(这个因数的倍数不能有93、96、99、102、105) 80和30 公因数 桌子上放着一个空烧杯,烧杯内有——A氮气 B氧气 C空气 D包括ABC 8和10的公因数 75和60 所有公因数的和 科学 (24 21:49:28)下列几种用电器正常工作时,电流最大的是(  )A.42寸液晶电视机   B.MP3       C.电子手表            D.100瓦 8和24的公因数是什么 90和60的公因数有哪些 科学 (24 16:22:14)T℃时,氯化钠的溶解度是n克,将m克氯化钠投入到n克水中,充分搅拌,若得到不饱和溶液,则溶液的溶质质量分数为(   ).如果m克氯化钠不能完全溶解在n克水中,则溶液的溶 8和5的公因数 16和25的公因数 某同学质量为60㎏,每只脚与地面的接触面积150cm²,当他站在水平地面上时,对地面的压强多大?若他想使自己对地面的压强增大一倍,他可以怎样做? 8和32的公因数 45,75和60的公因数 初中物理压强计算题初中物理的压强计算题 46和13 公因数 24和54的公因数有哪些 几道初中物理计算题 压强1.动物保护组织成员测量出野牛在水平沙地上站立时留下的一个脚印的面积为0.025立方米,深度为1.8CM,并对该沙地进行抗压实验发现使沙地达到相同深度的压强为2x10x10 14和46的最小公因数 30和35的公因数有哪些 一位体重为50千克的中学生每只脚与地面的接触面积为200平方厘米,那么他双脚站在水平面上,对地面的压强是多少? 1-99的公因数是什么?可加我好友我现场问一下 科学 (26 19:5:8)一位同学用天平和量筒测定不规则木块的密度,已知木头的密度小于水的密度.请写出所需实验器材和实验步骤,并写出木块密度表达式.  一道初中物理压强计算题把一个长方体平放、侧放、立放在水平桌面上,物块对水平桌面的压强分别是0.8×1000Pa、2×1000Pa、5×1000Pa,已知ρ物=2000kg/立方米,计算这个物块的体积(g取10牛/千克) 公因数是什么意思? 1、将干燥纯净的氯酸钾13.25KG与3.25KGMNO2混合后装入大试管知趣氧气,反应一段时间后得到11.7固体物质.(1)制的的氧气质量是多少?(2)反应后固体物质是什么?质量是多少?(1)4.8KG  (2) 求初二物理关于固体压强的两道试题及解答.要有详细的过程哦.谢谢大家咯.我需要的是计算题和过程。不是填空之类的哦。 625和560有没有公因数?(1除外!)(要最大的) 科学家用_、_及_来描述与研究运动. 初二物理光现象试题和答案.过程.最好有实验 光现象的 285和175有没有公因数?(1除外!)(要最大的) 科学题 (23 21:41:24)1.水晶是晶体吗?2.晶体一般具有______、________,而非晶体一般不具有这些特性.3.无水硫酸铜粉末是____色的,遇水后形成的硫酸铜晶体是____色的,把其溶解在水中形成的硫酸铜溶 人站在水平地面上时,保持静止,受到__力和____力的作用,这两个力的施力物体分别是_____和 _____.这两个力的关系是__________.悬挂在楼板下的电灯,受到地球对它的_____和电线对它的_____作用,这两 498和415有没有公因数(1除外) 科学 (23 16:47:21)一辆长30m的大型平板车,整两车匀速通过70m长的桥,所用的时间是10s,它以同样的速度通过另一座桥,花了20s时间.那么,另一座桥长多少? 〈声现象〉声音的特性和 噪音的危害和控制 285和175有没有公因数?(1除外!) 科学 (23 21:28:35)有一铝球,体积是60cm2,质量是108g,判断该球是空心还是实心?如果该球是空心,那么球内的中空部分的体积是多大?如果在中空部分铸满铁,那么这个球的质量为多少?(铝的密度:2.7 求一套初二物理期末试题 一定要有答案啊,两套最好了.直接复制到下面,急 ,这套题不太好,还有没有啊 1和3有没有公因数求快 生活中遇到的科学问题 (21 21:17:30)春节前几天家里的墙壁都很潮湿,这是为什么?那卫生么有有些地方不这样呢? 帮忙找几道初二物理练习题吧,最好有答案
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn