热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

这篇文章主要介绍了用Python进行行为驱动开发的入门教程,本文也对BDD的概念做了详细的解释,需要的朋友可以参考下

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

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

什么是BDD?

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

2015423101858798.jpg (600×241)

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


推荐阅读
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • 2022年的风口:你看不起的行业,真的很挣钱!
    本文介绍了2022年的风口,探讨了一份稳定的副业收入对于普通人增加收入的重要性,以及如何抓住风口来实现赚钱的目标。文章指出,拼命工作并不一定能让人有钱,而是需要顺应时代的方向。 ... [详细]
  • C++语言入门:数组的基本知识和应用领域
    本文介绍了C++语言的基本知识和应用领域,包括C++语言与Python语言的区别、C++语言的结构化特点、关键字和控制语句的使用、运算符的种类和表达式的灵活性、各种数据类型的运算以及指针概念的引入。同时,还探讨了C++语言在代码效率方面的优势和与汇编语言的比较。对于想要学习C++语言的初学者来说,本文提供了一个简洁而全面的入门指南。 ... [详细]
  • 导读:在编程的世界里,语言纷繁多样,而大部分真正广泛流行的语言并不是那些学术界的产物,而是在通过自由发挥设计出来的。和那些 ... [详细]
  • 与.Net大师Jeffrey Richter面对面交流——TUP对话大师系列活动回顾(多图配详细文字)...
    与.Net大师JeffreyRichter面对面交流——TUP对话大师系列活动回顾(多图配文字)上周末很有幸参加了CSDN举行的TUP活动, ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 本文介绍了Python函数的定义与调用的方法,以及函数的作用,包括增强代码的可读性和重用性。文章详细解释了函数的定义与调用的语法和规则,以及函数的参数和返回值的用法。同时,还介绍了函数返回值的多种情况和多个值的返回方式。通过学习本文,读者可以更好地理解和使用Python函数,提高代码的可读性和重用性。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • gitlab重置password
    ruby没怎么学,自己搭建的gitlab的rootpassword又忘了。幸好看见此帖子,试验okhttp:roland.kierkels.netgitreset-your-git ... [详细]
  • 开发笔记:Python之路第一篇:初识Python
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Python之路第一篇:初识Python相关的知识,希望对你有一定的参考价值。Python简介& ... [详细]
  • Rubyonrails开发人员在这里。此问题曾被问过。 ... [详细]
  • 1、概述首先和大家一起回顾一下Java消息服务,在我之前的博客《Java消息队列-JMS概述》中,我为大家分析了:然后在另一篇博客《Java消息队列-ActiveMq实战》中 ... [详细]
  • 有没有类似于ruby那样的方式number=5“Thisnumberis#{number}.“#=“Thisnumberis5.“ ... [详细]
  • 使用CocoaPods管理依赖库
    这篇内容将介绍Mac和iOS开发中必备的一个依赖库管理工具CocoaPods。在iOS开发中势必会用到一些第三方依赖库,比如大家都熟悉的ASIHttpRequest、AFNetwo ... [详细]
  • 让你明白kvm是什么
    参考:https:blog.csdn.netbbwangjarticledetails80465320KVM工具集合:libvirt:操作和管理KVM虚机的虚拟化API ... [详细]
author-avatar
happy柒月卍520
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有