用Python进行行为驱动开发的入门教程


Posted in Python onApril 23, 2015

为驱动开发(Behavior-Driven Development,BDD)是一种卓越的开发模式。能帮助开发者养成日清日结的好习惯,从而避免甚至杜绝“最后一分钟”的情况出现,因此对提高代码质量是大有裨益的。其与Gherkin语法相结合的测试结构及设计形式,使得对团队的全部成员包括非技术人员都具有极好的易读性。

所有代码都必须进行测试,这意味着上线时把系统瑕疵降到最低甚至为零。这需要与完整的测试套件相配,从整体把控软件行为,使得检测与维护都能有序进行。这就是BDD的魅力所在,难道不心动吗?

什么是BDD?

BDD的概念和理论源自TDD(测试驱动开发),类似于TDD的理论要点是在编码前先写好测试。不同点是除了使用单元测试进行细粒度化测试,还使用接受测试(acceptance tests)贯穿程序始末。接下来我们会结合Lettuce测试框架进行讲解。

用Python进行行为驱动开发的入门教程

BDD过程可简单概括为:

  •     编写一个缺陷接受测试
  •     编写一个缺陷单元测试
  •     使单元测试通过
  •     重构
  •     使接受测试通过

在每个功能里,如有需要重复上述步骤。

敏捷开发中的BDD

在敏捷开发中,BDD更是如鱼得水。

如果项目的新功能和新需求每隔一、两个星期就发生变更,那么该团队需要配合进行快节奏的测试和编码工作。Python中的接受和单元测试可以帮助实现该目标。

接受测试为人熟知的是使用了一个英文格式的“特性”描述文件,内容是含有的测试以及个别测试。这样做的好处是使整个项目团队都参与其中,除了开发者,还有管理者与商业分析者等不参与实际测试过程的非技术成员。

特性文件的编写遵循全员可读的规则,使技术和非技术成员都能清楚理解与接收。如果只包含单元测试,那么有可能会导致需求分析不全面或不能达成共识。接受测试的最大优点是适用性强,不论项目规模大小都能运用自如。

Gherkin语法

通常会使用Gherkin来编写接受测试,Gherkin来自Cucumber框架,由Ruby语言所编写。Gherkin语法十分简单,在Lettuce Python中主要使用以下8点来进行特性和测试的定义:

  •     Given假设
  •     When时间
  •     Then下一步
  •     And与
  •     Feature特性:
  •     Background背景:
  •     Scenario Outline场合大纲:

安装

使用Python常用的pip install语句就可完成Lettuce包的安装:
 

$ pip install lettuce

$ lettuce /path/to/example.feature用于运行测试。可以每次只运行一个测试文件,或者是提交目录名来运行目录下的所有文件。

为了使测试的编写和使用更加容易,我们建议把nosetests也安装好:
 

$ pip install nose

特性文件

特性文件由英语写成,内容是测试所覆盖的程序范围。此外还包括测试的创建任务。换言之,你除了需要编写测试,还得规范自己就程序的方方面面编写出良好的文档。这样做的好处是使自己对代码上下都心中有数,明确下一步做什么。随着项目规模的扩大,文档的重要性会逐步显现;例如重新回顾某个功能或对某个调用API进行回溯等等。

接下来会结合TDD中的一个实例创建一个特性文件。该实例是一个由Python写成的简易计算器,同时会演示接受测试的基本写法。目录构成的建议是建立两个文件夹,一个是app,用于放置代码文件如calculator.py;另一个是tests,用于放置特性文件夹。

calculator.py:
 
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

tests/features目录下的特性文件calculator.feature
 

Feature: As a writer for NetTuts
 I wish to demonstrate
 How easy writing Acceptance Tests
 In Python really is.
 
 Background:
  Given I am using the calculator
 
 Scenario: Calculate 2 plus 2 on our calculator
  Given I input "2" add "2"
  Then I should see "4"

从该例子不难看出特性文件的描述是非常直截了当的,能够使全体成员都能看明白。

特性文件的三个要点:

  •     Feature block(特性区块):该处描述了测试组所涵盖的程序内容。这里不执行任何代码,但能使阅读者明白正要进行什么样的特性测试。
  •     Background block(背景区块):先于特性文件中每个场景(Scenario)区块执行。这类似于SetUp()方法用于进行创建代码的编写,例如进行条件和位置的编写。
  •     Scenario block(场景区块):这里用于定义测试。第一行用作文档再一次的描述,接着是测试的具体内容。以这样的风格编写测试难道不是很简单吗?

步骤(Steps)文件

除了特性文件,步骤文件也是必须的,这是“见证奇迹的时刻”。显然地,特性文件本身不会做出什么结果;它需要步骤文件依次地与Python执行代码一一映射才有最后的结果输出。这里应用的是正则表达式。

正则表达式?不会过于复杂吗?其实在BDD世界里,正则表达式常用于捕捉整个字符串或从某行抓取变量。所以熟能生巧。

正则表达式?在测试中使用不会太复杂吗?在Lettuce是不会的,反而是非常简单的。

以下是对应的步骤文件的编写:
 

from lettuce import *
from nose.tools import assert_equals
from app.calculator import Calculator
 
 
@step(u'I am using the calculator')
def select_calc(step):
  print ('Attempting to use calculator...')
  world.calc = Calculator()
 
 
@step(u'I input "([^"]*)" add "([^"]*)"')
def given_i_input_group1_add_group1(step, x, y):
  world.result = world.calc.add(int(x), int(y))
 
 
@step(u'I should see "([^"]+)"')
def result(step, expected_result):
  actual_result = world.result
  assert_equals(int(expected_result), actual_result)

文件首部分是标准的导入写法。例如对Calculator的访问和Lettuce工具的导入,还有nosetest包中assert_equals断定方法的导入。接下来,你就可以开始针对特性文件的每一行进行步骤定义。如前所述,正则表达式很多时候用于提取整个字符串,除了有时需要在某行对变量进行访问。

在这个例子中, 里的@step起到解码提取的作用;u字母的意思是以unicode编码方式进行表达式执行。接着是使用正则表达式对引用的内容进行匹配,这里是要进行相加的数字。

然后是对Python方法传入变量,变量名可任意定义,这里使用x和y作为calculator add方法的传入变量名。

此外需要介绍world变量的使用。world是一个全局容器,使得变量可以在同一场景的不同步骤中使用。否则,所有变量只对应于其所在方法可用。例如把add方法的运算结果存放于某个step,而在另一外一个step进行结果的断定。

特性的执行

特性文件和步骤文件都完成后,接下来可以运行测试来看看能否通过。内建测试运行机的Lettuce执行方式是很简单的,例如

lettuce test/features/calculator.feature:
 
$ lettuce tests/features/calculator.feature 
 
Feature: As a writer for NetTuts         # tests/features/calculator.feature:1
 I wish to demonstrate             # tests/features/calculator.feature:2
 How easy writing Acceptance Tests       # tests/features/calculator.feature:3
 In Python really is.              # tests/features/calculator.feature:4
 
 Background:
  Given I am using the calculator       # tests/features/steps.py:6
  Given I am using the calculator       # tests/features/steps.py:6
 
 Scenario: Calculate 2 plus 2 on our calculator # tests/features/calculator.feature:9
  Given I input "2" add "2"          # tests/features/steps.py:11
  Then I should see "4"            # tests/features/steps.py:16
 
1 feature (1 passed)
1 scenario (1 passed)
2 steps (2 passed)

Lettuce的输出是非常工整的,它清楚显示了哪行特性文件代码被执行了,然后对成功执行的行以高亮绿色显示。此外还显示了正在运行的特性文件以及行号,这对于测试失败时进行特性文件缺陷行的查找是很有帮助的。输出末尾是特性,场景,步骤的执行个数以及通过个数的结果汇总。本例中所有测试都通过了。但如果出现错误,Lettuce会如何处理呢?

首先得对calculator.py代码进行修改,把add方法改为两数相减:
 

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

再次运行,看看Lettuce是如何对错误进行说明的:
 

$ lettuce tests/features/calculator.feature 
 
Feature: As a writer for NetTuts         # tests/features/calculator.feature:1
 I wish to demonstrate             # tests/features/calculator.feature:2
 How easy writing Acceptance Tests       # tests/features/calculator.feature:3
 In Python really is.              # tests/features/calculator.feature:4
 
 Background:
  Given I am using the calculator       # tests/features/steps.py:6
  Given I am using the calculator       # tests/features/steps.py:6
 
 Scenario: Calculate 2 plus 2 on our calculator # tests/features/calculator.feature:9
  Given I input "2" add "2"          # tests/features/steps.py:11
  Then I should see "4"            # tests/features/steps.py:16
  Traceback (most recent call last):
   File "/Users/user/.virtualenvs/bdd-in-python/lib/python2.7/site-packages/lettuce/core.py", line 144, in __call__
    ret = self.function(self.step, *args, **kw)
   File "/Users/user/Documents/Articles - NetTuts/BDD_in_Python/tests/features/steps.py", line 18, in result
    assert_equals(int(expected_result), actual_result)
   File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/case.py", line 515, in assertEqual
    assertion_func(first, second, msg=msg)
   File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/case.py", line 508, in _baseAssertEqual
    raise self.failureException(msg)
  AssertionError: 4 != 0
 
1 feature (0 passed)
1 scenario (0 passed)
2 steps (1 failed, 1 passed)
 
List of failed scenarios:
 Scenario: Calculate 2 plus 2 on our calculator # tests/features/calculator.feature:9

显然,实际结果0与预期结果4是不符的。Lettuce清楚显示了该问题,接下来就是调试排错直到通过的时间了。

其它工具
在Python中还提供了很多不同的工具来进行类似的测试,这些工具基本源自Cucumber。例如:

  •     Behave:这是一个Cucumber接口。文档配套齐备,保持更新,有不少的配套工具。
  •     Freshen:另一个Cucumber接口,配套网站有完整的教程和实例,安装方式都是简单的pip方式。

不论使用什么工具,只要对某个工具运用熟练了,其它的自然能融会贯通。对教程文档的熟读是成功的第一步。

优点

自信地进行代码重构

使用一个完整测试套件的优点是显而易见的。找到一个强大的测试套件,会让代码重构工作事半功倍,信心满满。

随着项目规模的不断扩大,如果缺乏有效的工具,这不啻会使回溯和重构工作困难重重。如果有一套完整的接受测试来与每个特性一一对应,那么将能使变更工作有序不紊地进行,不会对现有功能模块造成破坏。

全员都能参与其中的接受测试,将能极大地提升团队战斗力,一开始就朝着同一目标前进。程序员可把精力用在精确的目标上,避免需求范围的失控;测试员可就特性文件进行一一检阅,把测试环节做到极致。最后形成良性循环,使得程序的每个特性都完美交付。

综述

结合上述过程和工具,在过往工作过的团队中我们都曾取得不错的成绩。BDD开发方式可使整个团队保持专注,保持自信,保持活力,并使潜在错误降到最低。

Python 相关文章推荐
Python自定义scrapy中间模块避免重复采集的方法
Apr 07 Python
Python正则表达式使用范例分享
Dec 04 Python
python多进程和多线程究竟谁更快(详解)
May 29 Python
使用Kivy将python程序打包为apk文件
Jul 29 Python
python 构造三维全零数组的方法
Nov 12 Python
对python多线程中互斥锁Threading.Lock的简单应用详解
Jan 11 Python
python获取磁盘号下盘符步骤详解
Jun 19 Python
Python Gitlab Api 使用方法
Aug 28 Python
python 多进程并行编程 ProcessPoolExecutor的实现
Oct 11 Python
Django 实现外键去除自动添加的后缀‘_id’
Nov 15 Python
Python 实现将大图切片成小图,将小图组合成大图的例子
Mar 14 Python
Django url 路由匹配过程详解
Jan 22 Python
python正常时间和unix时间戳相互转换的方法
Apr 23 #Python
python执行等待程序直到第二天零点的方法
Apr 23 #Python
在Python中测试访问同一数据的竞争条件的方法
Apr 23 #Python
python实现在每个独立进程中运行一个函数的方法
Apr 23 #Python
python输出指定月份日历的方法
Apr 23 #Python
python打开文件并获取文件相关属性的方法
Apr 23 #Python
Python实现计算文件夹下.h和.cpp文件的总行数
Apr 23 #Python
You might like
全国FM电台频率大全 - 11 浙江省
2020/03/11 无线电
极典R601SW收音机
2021/03/02 无线电
多php服务器实现多session并发运行
2006/10/09 PHP
解决MySQL中文输出变成问号的问题
2008/06/05 PHP
那些年一起学习的PHP(一)
2012/03/21 PHP
PHP实现扎金花游戏之大小比赛的方法
2015/03/10 PHP
php实现微信公众号主动推送消息
2015/12/31 PHP
常用参考资料(手册)下载或者链接
2006/07/22 Javascript
更优雅的事件触发兼容
2011/10/24 Javascript
再JavaScript的jQuery库中编写动画效果的指南
2015/08/13 Javascript
Javascript将字符串日期格式化为yyyy-mm-dd的方法
2016/10/27 Javascript
函数四种调用模式以及其中的this指向
2017/01/16 Javascript
vuejs事件中心管理组件间的通信详解
2017/08/09 Javascript
基于JavaScript实现新增内容滚动播放效果附完整代码
2017/08/24 Javascript
Vue刷新修改页面中数据的方法
2018/09/16 Javascript
AjaxFileUpload.js实现异步上传文件功能
2019/04/19 Javascript
vue 解决遍历对象显示的顺序不对问题
2019/11/07 Javascript
vue中路由跳转不计入history的操作
2020/09/21 Javascript
[31:00]2014 DOTA2华西杯精英邀请赛5 24 NewBee VS iG
2014/05/25 DOTA
python模拟鼠标拖动操作的方法
2015/03/11 Python
使用Python来开发Markdown脚本扩展的实例分享
2016/03/04 Python
谈谈python中GUI的选择
2018/03/01 Python
Python函数中参数是传递值还是引用详解
2019/07/02 Python
浅谈django channels 路由误导
2020/05/28 Python
Python变量及数据类型用法原理汇总
2020/08/06 Python
如何让PyQt5中QWebEngineView与JavaScript交互
2020/10/21 Python
英国打印机墨水和碳粉商店:Printerinks
2017/06/30 全球购物
《称象》教学反思
2014/04/25 职场文书
乡镇党的群众路线对照检查材料
2014/09/24 职场文书
学生检讨书范文
2014/10/30 职场文书
国庆节慰问信
2015/02/15 职场文书
加薪通知
2015/04/25 职场文书
Python 线程池模块之多线程操作代码
2021/05/20 Python
原生JS实现飞机大战小游戏
2021/06/09 Javascript
maven依赖的version声明控制方式
2022/01/18 Java/Android
Java中API的使用方法详情
2022/04/06 Java/Android