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

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

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

什么是BDD?

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

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开发方式可使整个团队保持专注,保持自信,保持活力,并使潜在错误降到最低。

(0)

相关推荐

  • Python编程入门的一些基本知识

    Python与Perl,C和Java语言等有许多相似之处.不过,也有语言之间有一些明确的区别.本章的目的是让你迅速学习Python的语法. 第一个Python程序: 交互模式编程: 调用解释器不经过脚本文件作为参数,显示以下提示: $ python Python 2.6.4 (#1, Nov 11 2014, 13:34:43) [GCC 4.1.2 20120704 (Red Hat 5.6.2-48)] on linux2 Type "help", "copyright&

  • 在Python的Flask框架中使用模版的入门教程

     概述 如果你已经阅读过上一个章节,那么你应该已经完成了充分的准备工作并且创建了一个很简单的具有如下文件结构的Web应用:   microblog     |-flask文件夹     |-<一些虚拟环境的文件>     |-app文件夹     |  |-static文件夹     |  |-templates文件夹     |  |-__init__.py文件     |  |-views.py文件     |-tmp文件夹     |-run.py文件 亲,想要运行这个程序么?那就运行这

  • Python基础入门之seed()方法的使用

    seed() 设置生成随机数用的整数起始值.调用任何其他random模块函数之前调用这个函数. 语法 以下是seed()方法的语法: seed ( [x] ) 注意:此函数是无法直接访问的,所以需要导入seed模块,然后需要使用random静态对象来调用这个函数. 参数 x -- 这是下一个随机数的种子.如果省略,则需要系统时间,以产生下一个随机数. 返回值 此方法不返回任何值. 例子 下面的例子显示了seed()方法的使用. #!/usr/bin/python import random ra

  • python绘图方法实例入门

    本文实例讲述了python绘图方法.分享给大家供大家参考.具体如下: # -*- coding:utf-8 -*- import matplotlib.pyplot as plt def main(): # 颜色列表 colorList = ['b','g','r','c','m','y','k'] # 共用的横坐标 threadList = [1,2,4,8,10] # 设置横坐标和纵坐标的名称 plt.xlabel('threads') plt.ylabel('concurrent') #

  • Python入门教程之if语句的用法

    Python中的if语句是类似的其它语言的. if语句包含使用该数据进行比较,并根据比较的结果做出了决定的逻辑表达式. 语法: if语句在Python编程语言的语法是: if expression: statement(s) 如果布尔表达式的计算结果为true,那么if语句块将被执行.如果if语句布尔表达式计算为false,那么第一组代码将被执行. Python编程语言的假定任何非零和非null为true,如果是zero或null,则假定为false值. 例子: #!/usr/bin/pytho

  • 最基础的Python的socket编程入门教程

    本文介绍使用Python进行Socket网络编程,假设读者已经具备了基本的网络编程知识和Python的基本语法知识,本文中的代码如果没有说明则都是运行在Python 3.4下. Python的socket功能封装在socket库中,要使用socket,记得先import socket,socket库的详细介绍参见官方文档. 创建Socket 首先创建一个socket,使用socket库中得socket函数创建. import socket # create an INET, STREAM soc

  • Python简明入门教程

    本文实例讲述了Python简明入门教程.分享给大家供大家参考.具体如下: 一.基本概念 1.数 在Python中有4种类型的数--整数.长整数.浮点数和复数. (1)2是一个整数的例子. (2)长整数不过是大一些的整数. (2)3.23和52.3E-4是浮点数的例子.E标记表示10的幂.在这里,52.3E-4表示52.3 * 10-4. (4)(-5+4j)和(2.3-4.6j)是复数的例子. 2.字符串 (1)使用单引号(') (2)使用双引号(") (3)使用三引号('''或"&q

  • Python入门之modf()方法的使用

    modf()方法返回两个项的元组x的整数小数部分.这两个元组具有相同x符号.则返回一个浮点数的整数部分. 语法 以下是modf()方法的语法: import math math.modf( x ) 注意:此函数是无法直接访问的,所以我们需要导入math模块,然后需要用math的静态对象来调用这个函数. 参数 x -- 这是一个数值表达式 返回值 这种方法在两项的元组返回x的整数小数部分.具有相同的x符号.整数部分则返回一个浮点数. 例子 下面的例子显示了modf()方法的使用. #!/usr/b

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

    为驱动开发(Behavior-Driven Development,BDD)是一种卓越的开发模式.能帮助开发者养成日清日结的好习惯,从而避免甚至杜绝"最后一分钟"的情况出现,因此对提高代码质量是大有裨益的.其与Gherkin语法相结合的测试结构及设计形式,使得对团队的全部成员包括非技术人员都具有极好的易读性. 所有代码都必须进行测试,这意味着上线时把系统瑕疵降到最低甚至为零.这需要与完整的测试套件相配,从整体把控软件行为,使得检测与维护都能有序进行.这就是BDD的魅力所在,难道不心动吗

  • python第三方库visdom的使用入门教程

    概述 Visdom:一个灵活的可视化工具,可用来对于 实时,富数据的 创建,组织和共享.支持Torch和Numpy还有pytorch. visdom 可以实现远程数据的可视化,对科学实验有很大帮助.我们可以远程的发送图片和数据,并进行在ui界面显示出来,检查实验结果,或者debug. 要用这个先要安装,对于python模块而言,安装都是蛮简单的: pip install visdom 安装完每次要用直接输入代码打开: python -m visdom.server 然后根据提示在浏览器中输入相应

  • Java搭建简单Netty开发环境入门教程

    下面就是准备Netty的jar包了,如果你会maven的话自然是使用maven最为方便了.只需要在pom文件中导入以下几行 <!-- https://mvnrepository.com/artifact/io.netty/netty-all --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1

  • Python自然语言处理 NLTK 库用法入门教程【经典】

    本文实例讲述了Python自然语言处理 NLTK 库用法.分享给大家供大家参考,具体如下: 在这篇文章中,我们将基于 Python 讨论自然语言处理(NLP).本教程将会使用 Python NLTK 库.NLTK 是一个当下流行的,用于自然语言处理的 Python 库. 那么 NLP 到底是什么?学习 NLP 能带来什么好处? 简单的说,自然语言处理( NLP )就是开发能够理解人类语言的应用程序和服务. 我们生活中经常会接触的自然语言处理的应用,包括语音识别,语音翻译,理解句意,理解特定词语的

  • Python爬虫PyQuery库基本用法入门教程

    本文实例讲述了Python爬虫PyQuery库基本用法.分享给大家供大家参考,具体如下: PyQuery库也是一个非常强大又灵活的网页解析库,如果你有前端开发经验的,都应该接触过jQuery,那么PyQuery就是你非常绝佳的选择,PyQuery 是 Python 仿照 jQuery 的严格实现.语法与 jQuery 几乎完全相同,所以不用再去费心去记一些奇怪的方法了. 官网地址:http://pyquery.readthedocs.io/en/latest/ jQuery参考文档: http:

  • Python爬虫框架Scrapy基本用法入门教程

    本文实例讲述了Python爬虫框架Scrapy基本用法.分享给大家供大家参考,具体如下: Xpath <html> <head> <title>标题</title> </head> <body> <h2>二级标题</h2> <p>爬虫1</p> <p>爬虫2</p> </body> </html> 在上述html代码中,我要获取h2的内容,

  • Python 强大的信号库 blinker 入门详细教程

    目录 1.信号 2.blinker使用 2.1命名信号 2.2匿名信号 2.3组播信号 2.4接收方订阅主题 2.5装饰器用法 2.6可订阅主题的装饰器 2.7检查信号是否有接收者 2.8检查订阅者是否订阅了某个信号 3.基于blinker的Flask信号 3.1简单Flaskdemo 3.2自定义信号 3.3Flask自带信号 4.总结 1.信号 信号是一种通知或者说通信的方式,信号分为发送方和接收方.发送方发送一种信号,接收方收到信号的进程会跳入信号处理函数,执行完后再跳回原来的位置继续执行

  • Python OpenCV快速入门教程

    OpenCV OpenCV是计算机视觉中最受欢迎的库,最初由intel使用C和C ++进行开发的,现在也可以在python中使用.该库是一个跨平台的开源库,是免费使用的.OpenCV库是一个高度优化的库,主要关注实时应用程序. OpenCV库是2500多种优化算法的组合,可用于检测和识别不同的人脸,实时识别图像中的对象,使用视频和网络摄像头对不同的人类动作进行分类,跟踪摄像机的运动,跟踪运动对象(例如汽车,人等),实时计数对象,缝合图像来产生高分辨率图像,从图像数据库中查找相似的图像,从使用闪光

  • 深度定制Python的Flask框架开发环境的一些技巧总结

    Flask 环境配置 你的应用程序可能需要大量的软件包才能正常的工作.如果都不需要 Flask 包的话,你有可能读错了教程.当应用程序运行的时候,你的应用程序的 环境 基本上是所有一切事情的根基.我们是幸运的,因为有许多方式使得我们能够轻松地管理我们的环境. 使用 virtualenv 管理你的环境 virtualenv是用于在所谓 虚拟环境 中隔离你的应用程序的一个工具.一个虚拟环境是包含了你的应用依赖的软件的一个目录.一个虚拟环境也能够改变你的环境变量以维持你的开发环境包含的环境变量.不用下

  • python爬虫(入门教程、视频教程) 原创

    python的版本经过了python2.x和python3.x等版本,无论哪种版本,关于python爬虫相关的知识是融会贯通的,我们关于爬虫这个方便整理过很多有价值的教程,小编通过本文章给大家做一个关于python爬虫相关知识的总结,以下就是全部内容: python爬虫的基础概述 1.什么是爬虫 网络爬虫,即Web Spider,是一个很形象的名字.把互联网比喻成一个蜘蛛网,那么Spider就是在网上爬来爬去的蜘蛛.网络蜘蛛是通过网页的链接地址来寻找网页的.从网站某一个页面(通常是首页)开始,读

随机推荐