基于Python的**驱动开发实战

近年来**驱动开发(TDD)受到越来越多的关注。这是一个持续改进的过程,能从一开始就形成规范,帮助提高代码质量。这是切实可行的而非天马行空的。TDD的全过程是非常简单的。借助TDD,代码质量会得到提升,同时可以让你保持清晰的思路。TDD与敏捷开发可谓强强联合,特别是在进行结对编程的时候。本文主要介绍了TDD的核心概念,还有结合nose**单元**包进行Python示例简析。另外还会介绍一些Python备用包。**TDD****是什么?**使用该方法可让你少走前人的弯路顾名思义,TDD即进行编程时先把**部分写好,当发现不能通过时,再进行编程以使**通过。然后在这基础上适当地调整**代码以实现更多功能,最后再编写代码使之实现。TDD看起来非常像一个环,首先是要不断调整**代码,然后是编码,改进,最后直至完成。先实现**部分的做**使你自然养成把问题放在首位的思维习惯。当真正去构建代码时,就不得不想清楚该如何把设计做好;比方说,该方法有何返回值?当遇到异常时该怎么办?诸如此类。以这样的方式进行开发,意味着要想出不同的代码实现路径,并在**中进行实践。这样做可使你少走前人的弯路:陷入一个问题后写出毫不相关的解决方案。**://img.ptcms.csdn.net/article/201502/16/54e1d785d533b.jpg
该过程可描述如下:

写出一个缺陷单元**
使该单元**通过
重构

**与敏捷开发结合**TDD与敏捷开发并行不悖甚至1+1远大于2,这里指的是代码质量而不是数量。**_“这意味着结对双方都会参与其中,着重于当前工作,然后在每个环节进行互检。”_**
然而在结对编程时TDD是单独进行的。如果能把双方的开发流程混合好,互相都能理解就最好不过了。例如,其中一人写出单元**,当**通过后,另外一人可以编写不同的**以之通过。任何时候结对双方都可以互换角色,每半天或天。这意味着结对双方都会参与其中,每人都把精力放在当前任务上,然后在每个环节进行交叉互检。这难道不是一个双赢的做法吗?TDD也可以是行为驱动开发过程中的组成部分,同样地,首先写出**,只不过这里指的是接受**。这样有助于把工作从头到尾都保持规范。**单元**语法**进行单元**时,使用到的Python方法如下:

**ert: 编写个人声明的基本方式
**ertEqual(a,b):检查a和b的是否等价
**ertNotEqual(a,b):检查a和b的是否非等价
**ertIn(a,b):检查是否存在b中
**ertNotIn(a,b): 检查是否不存在b中
**ertFalse(a):检查a的值是否为False
**ertTrue(a):检查a的值是否为Ture
**ertIsInstance(a,TYPE):检查a是否为“TYPE”类型
**ertRaises(ERROR,a,args):以参数args调用a时,检查是否会出现ERROR

以上是实际当中使用频率最高的方法,更多的方法请查阅Python单元**文档。**安装并使用Python Nose**进行下面的练习前,请把nose****运行包安装好。使用标准pip语句进行安装是最直接的做法。此外在项目中使用VirtualEnv(Python虚拟环境)也是不错的做法,因为它可确保所有包在不同项目中是**的。假如对pip或VirtualEnv了解不多,不妨先查阅相关文档:VirtualEnv,PIP。pip语句十分简洁:
共6个回复

kaifu

2015-02-21 14:34

"pip install nose" 安装完成后,可以执行单个**文件 $ nose**s example_unit_**.py 或者可以直接执行文件夹中的文件组 $ nose**s /path/to/**s 这里要注意的是每个**方法都应以“**_”为开头,这样nose**运行机才能正确识别出目标**文件。 **可选参数**下面介绍几个有用的命令行参数: -v:输出更多信息,包括正在执行的**文件名; -s或-nocapture:进行PRINT语句输出,一般情况下这是隐藏的。开启后可方便调试; --nologcapture:输出日志信息; --rednose:一个可选**件,请点击这里下载,输出带颜色的输出信息; --tags=TAGS:指定要执行的**文件,而不是整个**文件组。 **实例分析和**驱动方法**接下来结合一个简单的计算器类例子例如相加/相减,来讲述Python单元**和TDD概念。对于add相加功能,会尝试编写一个缺陷**。在一个空白项目中,首先创建两个python包app和**。然后在每个文件里建立两个名为_init_.py空白文件。这是Phthon工程的标准结构,完成后可以拥有一个可导入的文件结构。如果需要了解更多有关文档架构的信息,请查阅Python包说明文档。 在**目录里创建一个**_calulator.py文件,其代码如下:

kaifu

2015-02-21 14:36

import unit** cl** TddInPythonExample(unit**.**Case): def **_calculator_add_method_returns_correct_result(self): calc = Calculator() result = calc.add(2,2) self.**ertEqual(4, result) 说明: 首先,从Python标准库里导入标准的unit**模块 接着,创建一个含有不同**用例的类 最后,创建以“**_”为开头的一个**方法 完成后可着手编写**代码了。执行方法前要先对计算器进行初始化,初始化完成后便可调用add方法,并把结果存入变量result中。完成后,使用unit**的**ertEqual方法来确保add方**常执行。 现在可以启动nose**来执行**文件了。代码如下: if __name__ == '__main__': unit**.main() 标准的Python文件执行方式为$ python **_calculator.py,相比之下本文使用的nose**s方**能更丰富,例如可以运行目录中的全部**文件。 $ nose**s **_calculator.py E ====================================================================== ERROR: **_calculator_add_method_returns_correct_result (**.**_calculator.TddInPythonExample) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/user/PycharmProjects/tdd_in_python/**/**_calculator.py", line 6, in **_calculator_add_method_returns_correct_result calc = Calculator() NameError: global name 'Calculator' is not defined ---------------------------------------------------------------------- Ran 1 ** in 0.001s FAILED (errors=1) 运行后可见出错的原因是没有导入Caculator。因为还没有创建呢!创建的方法是在app目录下建立calculator.py文件,然后导入: cl** Calculator(object): def add(self, x, y): p** import unit** from app.calculator import Calculator cl** TddInPythonExample(unit**.**Case): def **_calculator_add_method_returns_correct_result(self): calc = Calculator() result = calc.add(2,2) self.**ertEqual(4, result) if __name__ == '__main__': unit**.main() 把Caculator构建好之后,再次运行看会出现什么结果: $ nose**s **_calculator.py F ====================================================================== FAIL: **_calculator_add_method_returns_correct_result (**.**_calculator.TddInPythonExample) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/user/PycharmProjects/tdd_in_python/**/**_calculator.py", line 9, in **_calculator_add_method_returns_correct_result self.**ertEqual(4, result) **ertionError: 4 != None ---------------------------------------------------------------------- Ran 1 ** in 0.001s FAILED (failures=1) 很明显,add方法返回了错误的值,因为还没有为它指定行为。幸好nose**会指出出错的位置,方便进行修改。稍作改动后,**便可通过了: cl** Calculator(object): def add(self, x, y): return x+y $ nose**s **_calculator.py . ---------------------------------------------------------------------- Ran 1 ** in 0.000s OK 虽然通过了,但是围绕该方法还可以做更多的工作。 沉迷于某个案例很容易造成短视 如果进行非数字型数据相加会导致什么后果呢?事实上Python是允许字符串或其它类型进行相加的,但在我们的例子里不允许。接着尝试就这个例子加入另一个缺陷**,然后使用**ertRaises方法来判断是否有异常抛出:

kaifu

2015-02-21 14:38

import unit** from app.calculator import Calculator cl** TddInPythonExample(unit**.**Case): def setUp(self): self.calc = Calculator() def **_calculator_add_method_returns_correct_result(self): result = self.calc.add(2, 2) self.**ertEqual(4, result) def **_calculator_returns_error_message_if_both_args_not_numbers(self): self.**ertRaises(ValueError, self.calc.add, 'two', 'three') if __name__ == '__main__': unit**.main() 以上代码中,检查了是否引起了ValueError错误,其实还可以进行更多的检测,不过在这里不作深入讲述。此外,setup()方法用于推入计算对象。下面再看看nose**会反馈什么信息: $ nose**s **_calculator.py .F ====================================================================== FAIL: **_calculator_returns_error_message_if_both_args_not_numbers (**.**_calculator.TddInPythonExample) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/user/PycharmProjects/tdd_in_python/**/**_calculator.py", line 15, in **_calculator_returns_error_message_if_both_args_not_numbers self.**ertRaises(ValueError, self.calc.add, 'two', 'three') **ertionError: ValueError not raised ---------------------------------------------------------------------- Ran 2 **s in 0.001s FAILED (failures=1) 显然nose**s告诉我们ValueError没有被抛出。现在我们有了一个新的缺陷**,接着尝试编码进行解决: cl** 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 unit** from app.calculator import Calculator cl** TddInPythonExample(unit**.**Case): def setUp(self): self.calc = Calculator() def **_calculator_add_method_returns_correct_result(self): result = self.calc.add(2, 2) self.**ertEqual(4, result) def **_calculator_returns_error_message_if_both_args_not_numbers(self): self.**ertRaises(ValueError, self.calc.add, 'two', 'three') def **_calculator_returns_error_message_if_x_arg_not_number(self): self.**ertRaises(ValueError, self.calc.add, 'two', 3) def **_calculator_returns_error_message_if_y_arg_not_number(self): self.**ertRaises(ValueError, self.calc.add, 2, 'three') if __name__ == '__main__': unit**.main()

kaifu

2015-02-21 14:38

至此我们可以运行所有的**了,所要实现的需求也都满足了。 其它的单元**包 py.** py**的作用与nose**类似,不过可以在单独的区域里输出信息,这意味着能够使我们很快地看清楚命令行中出现的打印信息。这对于只运行单个**的情况是很有用的。

kaifu

2015-02-21 14:42

$ nose**s **_calculator.py .... ---------------------------------------------------------------------- Ran 4 **s in 0.001s OK 安装py**的方式与nose**差不多,命令是$ pip install pytes。执行的命令是$ pip install pytes或者指定要执行的**文件$ py.** **/calculator_**s.py。 $ py.** **/**_calculator.py ================================================================= ** session starts ================================================================= platform darwin -- Python 2.7.6 -- py-1.4.26 -- py**-2.6.4 collected 4 **s **/**_calculator.py .... ============================================================== 4 p**ed in 0.02 seconds =============================================================== py**运行后的结果如下。注:只有代码含有错误或异常的情况下,py**才会进行输出。 $ py.** **/**_calculator.py ================================================================= ** session starts ================================================================= platform darwin -- Python 2.7.6 -- py-1.4.26 -- py**-2.6.4 collected 4 **s **/**_calculator.py F... ====================================================================== FAILURES ======================================================================= ________________________________________ TddInPythonExample.**_calculator_add_method_returns_correct_result _________________________________________ self = <**.**_calculator.TddInPythonExample **Method=**_calculator_add_method_returns_correct_result> def **_calculator_add_method_returns_correct_result(self): result = self.calc.add(3, 2) > self.**ertEqual(4, result) E **ertionError: 4 != 5 **/**_calculator.py:11: **ertionError ---------------------------------------------------------------- Captured stdout call ----------------------------------------------------------------- X value is: 3 Y value is: 2 Result is 5 ========================================================= 1 failed, 3 p**ed in 0.03 seconds ========================================================== 单元** 如果不想安装额外的包并想保持一个纯净的标准库结构,使用Python内建的unit**单元**包是不错的选择。其使用方法如下: if __name__ == '__main__': unit**.main() 使用python calculator_**s.py执行后,看会得到什么结果: $ python **/**_calculator.py .... ---------------------------------------------------------------------- Ran 4 **s in 0.004s OK 使用PDB进行调试 以TDD方式开发,经常会遇到来自代码或**的问题。有时这些错误又是比较隐蔽的。因此,需要配合使用高明的调试技术。 以TDD方式进行开发出现问题时可能难以发现 幸运地,有不少的办法来解决这些问题。其中最简单的方式是透过增添print语句实现“断点”输出。 结合print语句进行调试 加法通过后,可以尝试进行减法调试。把app/calculator.py中的add部分代码作如下改动: cl** 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 这里不妨尝试使用print语句进行输出,来监视值是怎样变化的。 cl** Calculator(object): def add(self, x, y): number_types = (int, long, float, complex) if isinstance(x, number_types) and isinstance(y, number_types): print 'X is: {}'.format(x) print 'Y is: {}'.format(y) result = x - y print 'Result is: {}'.format(result) return result else: raise ValueError 现在可以使用nose**来执行并查看结果,可见这样的工整输出结构,对调试是十分有帮助的。 $ nose**s **/**_calculator.py F... ====================================================================== FAIL: **_calculator_add_method_returns_correct_result (**.**_calculator.TddInPythonExample) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/user/PycharmProjects/tdd_in_python/**/**_calculator.py", line 11, in **_calculator_add_method_returns_correct_result self.**ertEqual(4, result) **ertionError: 4 != 0 -------------------- >> begin captured stdout << --------------------- X is: 2 Y is: 2 Result is: 0 --------------------- >> end captured stdout << ---------------------- ---------------------------------------------------------------------- Ran 4 **s in 0.002s FAILED (failures=1) PDB进阶调试 如果遇到更复杂的调试环节,仅仅依**print语句是不够的。其中最经常使用的进阶调试工具是pdb(Python Debugger)。该工具包含在标准库中,使用的时候只需加入一行代码到“断点”位置。请**面的代码: cl** Calculator(object): def add(self, x, y): number_types = (int, long, float, complex) if isinstance(x, number_types) and isinstance(y, number_types): import pdb; pdb.set_trace() return x - y else: raise ValueError 请注意,如果使用nose**执行**,请务必使用-s标记,否则nose**会继续对输出进行抓取,这样会使pdb无**常运行。如果是使用unit**或py**则无需这样做。 如果**停止并有pdb提示,请使用list命令来进行当前代码定位。 $ nose**s -s > /Users/user/PycharmProjects/tdd_in_python/app/calculator.py(7)add() -> return x - y (Pdb) list 2 def add(self, x, y): 3 number_types = (int, long, float, complex) 4 5 if isinstance(x, number_types) and isinstance(y, number_types): 6 import pdb; pdb.set_trace() 7 -> return x - y 8 else: 9 raise ValueError (Pdb)

kaifu

2015-02-21 14:43

出现提示后是可以进行交互**作的,比方说想在这个时候检阅x和y的值:(Pdb) x 2 (Pdb) y 2 如果想了解更多命令,可以键入help来查看。经常使用的命令如下所示: n: 步进到下个执行 list: 显示当前位置 args: 显示在当前执行点上用到的变量 continue:运行代码直至结束 jump : 运行并跳转到行号位置 quit/exit:停止pdb **写在最后**TDD模式十分有趣同时能帮助提高代码质量。不论是大型团队还是个人开发,TDD都可运用其中。此外,成功的缺陷**设计是非常有满足感的。所以,不妨从今天起尝试把TDD引入到日常工作中,亲身体验试验前后会有什么变化。英文来自:code.tutsplus
点击此处继续提问与开发者们互动

申请试用

提交后工作人员会尽快与您联系进行功能演示
技术咨询已转移到管理后台,请先登录

即时通讯云使用者请在应用详情页面咨询
客服云使用者请进入管理员模式咨询

×

400电话

商务咨询

技术咨询

免费试用