行为驱动开发(BDD)你准备好了吗?

这里写图片描述

最近在研究行为驱动开发(Behavior Drive Development ), 那什么是BDD(行为驱动开发)?为什么使用行为驱动开发?用什么框架来做行为驱动开发?行为驱动开发的核心原理是?有行为驱动开发的例子吗? 敬请关注笔者的这片文章。

根据BDD in action一书的介绍,
Behavior-Driven Development (BDD) is a set of software engineering practices
designed to help teams build and deliver more valuable, higher quality software faster.
It draws on Agile and lean practices including, in particular, Test-Driven Development
(TDD) and Domain-Driven Design (DDD). But most importantly, BDD provides a common language based on simple, structured sentences expressed in English (or in the native language of the stakeholders) that facilitate communication between project team members and business stakeholders.

翻译成中文的大概意思就是,行为驱动开发是一个软件工程的系列实践,能够帮助团队快速构建和交付更多价值和质量的软件产品。其和敏捷已经精益的开发实践,是一脉相承的,特别是测试驱动开发,已经领域驱动开发。但是最重要的是BDD提供了一种通用的,简单的,结构化的描述语言,这种语言既可以是英语也可以是其他本地的语言,通过他能够很方便让项目成员和业务干系人非常顺畅的沟通需求,及时这些干系人不懂的任何编程语言。下面就是一个例子。

这里写图片描述

是不是很好读懂,上面其实就叫Feature(特性文件),其遵循的Gherkin标准,其有下面的关键字,很好理解,
• Feature
• Background
• Scenario
• Given
• When
• Then
• And
• But
• *
• Scenario Outline
• Examples
这种特性文件,客户,项目经理,BA,QA都能看懂,因为其就是一个故事点或者需求点,而且通过特定的工具,比如cucumber的工具,能够把其自动转换成为代码。开发人员根据自动生成的代码,断言一些预期的行为,并根据这些行为,完成相应的代码实现,在代码实现的基础上,进行重构; 这样就为一个项目中的各个人员了解项目的需求,实现提供了一个很好的交互桥梁。下面是其一个交互的过程。

这里写图片描述

如果是传统的方式,其交互方式,应该是,

这里写图片描述

通过对比,大家是不是发现BDD的这种方式,把用户或者客户真正的通过Feature文件联系在一起了,其沟通是顺畅的,QA,BA,开发,测试,客户,用户可以通过这一媒介,进行高效无障碍的沟通,而不是像传统的方式,通过BA进行二次转达,从而丢失了很多重要的需求。
由此可见,其BDD的好处如下:

  • 减少浪费
  • 节省成本
  • 容易并且安全的适应变化
  • 因为少了中间的转达环节,从而能够快速交付产品

下面看一个简单的例子,
这里写图片描述

从上图可以看出,当一个需求过来的时候,先通过项目干系人都能理解的Feature文件,描述项目的User Story, 有的里面还有详细生动的数据范例(example),从而能够让所有的人更加容易理解其需求, 比如,

这里写图片描述

通过上面的数据范例(example)的表格是不是更加容易的理解当前case的意图了。当Feature和Example文件都完成后,借助于第三方的开源框架实现,比如Cucumber,jBehave,SpecFlow等把Feature和Example装换成代码,然后通过第层次的单元测试框架,比如JUnit,NUnit,Spock,RSpec,结合测试驱动开发,从而把业务代码的逻辑实现。真是一举多得。

笔者就以Cucumber和JUnit为例,举一个BDD的例子吧。大家对JUnit比较熟悉,但是对Cucumber可能会相对陌生一点,笔者就花一点笔墨,简单介绍了一下Cucumber。Cucumer是一个实现了BDD的一个框架,其支持下面的语言和框架集成,

这里写图片描述

是不是感觉很强大啊!!!! 下面进入实战,

1. Maven库的依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>uk.co.claysnow</groupId>
  <version>1.0</version>
  <packaging>jar</packaging>
  <name>Cucumber-JVM Book ATM Example</name>
  <artifactId>atm-example</artifactId>

    <properties>
        <cucumber.version>1.2.0</cucumber.version>
        <junit.version>4.11</junit.version>
        <picocontainer.version>2.14.2</picocontainer.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-picocontainer</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.picocontainer</groupId>
            <artifactId>picocontainer</artifactId>
            <version>${picocontainer.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

  <build>
      <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.12.2</version>
            <configuration>
                <argLine>-Duser.language=en</argLine>
                <argLine>-Xmx1024m</argLine>
                <argLine>-XX:MaxPermSize=256m</argLine>
                <argLine>-Dfile.encoding=UTF-8</argLine>
                <useFile>false</useFile>
              </configuration>
          </plugin>

      </plugins>
  </build>

</project>
2. 安装Cucumber Eclipse插件

为了支持Feature的Gherkin语法,我们需要在Eclipse安装下面的插件:
https://cucumber.io/cucumber-eclipse/update-site
具体安装方法,请到百度或者google搜索。下面看一个具体的例子:

3. Feature文件
Feature: Cash Withdrawal
  Scenario: Successful withdrawal from an account in credit
    Given I have deposited $100.00 in my account
    When I withdraw $20
    Then $20 should be dispensed
4. 生成的Java Specification文件
import cucumber.api.java.en.*;
import cucumber.api.Transform;

import org.junit.*;

import support.KnowsTheDomain;
import transforms.MoneyConverter;

public class AccountSteps {
  KnowsTheDomain helper;

  public AccountSteps(KnowsTheDomain helper) {
    this.helper = helper;
  }

  @Given("^I have deposited (\\$\\d+\\.\\d+) in my account$")
  public void iHaveDeposited$InMyAccount(@Transform(MoneyConverter.class) Money amount) 
                                                              throws Throwable {
    helper.getMyAccount().deposit(amount);

    Assert.assertEquals("Incorrect account balance -", amount, helper.getMyAccount().getBalance()); 
  }
}
import cucumber.api.java.en.*;

import org.junit.*;

import support.KnowsTheDomain;

import transforms.MoneyConverter;

public class CashSlotSteps {

  KnowsTheDomain helper;

  public CashSlotSteps(KnowsTheDomain helper) {
      this.helper = helper;
  }

  @Given("^\\$(\\d+) should be dispensed$")
  public void $ShouldBeDispensed(int dollars) throws Throwable {
    Assert.assertEquals("Incorrect amount dispensed -", dollars, 
                                    helper.getCashSlot().getContents());
  }
}
import cucumber.api.java.en.*;

import support.KnowsTheDomain;

public class TellerSteps {

  KnowsTheDomain helper;

  public TellerSteps(KnowsTheDomain helper) {
      this.helper = helper;
  }

  @When("^I withdraw \\$(\\d+)$")
  public void iWithdraw$(int amount) throws Throwable {
      helper.getTeller().withdrawFrom(helper.getMyAccount(), amount);
  }
}
5. Cucumber 测试的运行入口
import cucumber.api.junit.Cucumber;
import cucumber.api.CucumberOptions;
import cucumber.api.SnippetType;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
@CucumberOptions(plugin="pretty", snippets=SnippetType.CAMELCASE)
public class RunCukesTest {
}
6. 具体的实现
public class Account {
    private Money balance = new Money();

    public void deposit(Money amount) {
        balance = balance.add(amount);
    }

    public Money getBalance() {
      return balance;
    }
}
public class CashSlot {
    private int contents;

    public int getContents() {
        return contents;
    }

    public void dispense(int dollars){
        contents = dollars;
    }
}
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class Money {
    private final int dollars;
    private final int cents;

    public Money() {
        this.dollars = 0;
        this.cents = 0;
    }

    public Money(int dollars, int cents) {
        this.dollars = dollars;
        this.cents = cents;
    }

    public Money(String amount) {
        Pattern pattern = Pattern.compile("^[^\\d]*([\\d]+)\\.([\\d][\\d])$");
        Matcher matcher = pattern.matcher(amount);

        matcher.find();
        this.dollars = Integer.parseInt(matcher.group(1));
        this.cents = Integer.parseInt(matcher.group(2));
    }

    public int dollars() {
        return dollars;
    }

    public int cents() {
        return cents;
    }

    public Money add(Money amount){
        int newCents = cents + amount.cents();
        int newDollars = dollars + amount.dollars();

        if (newCents >= 100){
            newCents -= 100;
            newDollars++;
        }

        return new Money(newDollars, newCents);
    }

    public Money minus(Money amount){
        int newCents = cents - amount.cents();
        int newDollars = dollars - amount.dollars();

        if (newCents < 0){
            newCents += 100;
            newDollars--;
        }

        return new Money(newDollars, newCents);
    }

    @Override
    public boolean equals(Object other){
        boolean equal = false;

        if (other instanceof Money){
            Money otherMoney = (Money)other;
            equal = (this.dollars() == otherMoney.dollars() 
                       && this.cents() == otherMoney.cents());
        }

        return equal;
    }

    @Override 
    public String toString() {
        return String.format("$%01d.%02d", this.dollars(), this.cents());
    } 
}
public class Teller {
    private CashSlot cashSlot;

    public Teller(CashSlot cashSlot) {
        this.cashSlot = cashSlot;
    }

    public void withdrawFrom(Account account, int dollars) {
        cashSlot.dispense(dollars);
    }
}

从上面的例子可以看出,其实现的本质和原理如下:
这里写图片描述

夜深了,该睡了,行为驱动开发的介绍过一段落,行为驱动开发(BDD)你准备好了吗?

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
作者: [英]Matt Wynne / [挪]Aslak Hellesy 出版社: 人民邮电出版社 原作名: The Cucumber Book:Behaviour-Driven Development for Testers and Developers 译者: 许晓斌 / 王江平 这是一本半技术书籍,虽然是测试使用,但阅读它实在也需要一定的代码基础,所以可见,测试的技术含量越来越高了:D 由于本人使用Java,所以忽略了12章以后的内容,由于它所使用到的那些框架都是基于Ruby。 本书分为3部分: 1. 基础 2. 进阶 3. 应用 在第一部分,基础篇中,介绍了Gherkin语法,Cucumber的产生背景与适用范围,以及常见问题与解答。 Cucumber是一种系统行为的描述文件,它是活文档,应该时刻描述当前系统的正确行为,并且能够自动测试。 这一特性事实上也要求在写Cu..ber文件时,务必做到用户精准,不要重复场景,用书上的话来讲,就是同一句话,对且只对应系统中的唯一的一个行为。 Cu..ber主要用于在团队中进行沟通,语言必须能通用,要通用就要求隐藏技术细节,以自然语言去描述系统的行为,最经典的场景如: Given ... When ... Then ... 给定一定场景,当做什么操作时,会产生什么样的结果。 表格的使用,Backgroud关键字都是为了让特性文件能更简洁,也更易懂和富有表现力。 第二部分进阶篇中,介绍了一些高级的功能,比如: 钩子和标签 钩子是指 @Before @After 这种加上实现方法之前,在测试开始时和结束后执行一些特定的操作。Cu...ber的步骤是全局的,同理,@Be..这类钩子也是全局的,Cu...ber的全局是大有深意的,因为它认为,特性中的所有有用步骤,只能对应一种系统的行为。若需要让其支持单个场景,则需要对在钩子后面加上标签的方式。 标签同钩子形式相同,可以在场景和特性关键词上加标签。 对于特性(Feature)的标签,会加在每个场景上。 Cu...ber可以对一组标签进行测试。 Cu...ber测试中(可以推而广之到任何测试中),凡是有数据库参与,需要在测试之前保证数据库是干净的,并且当前测试不会遗留下数据影响到下一个测试。可以使用事务和Truncate的方式来保证这点,实际上,只要测试环节所需要的数据都由Given中提供,则不会有问题。 第三部分讲应用 ,基本上都是基于Ruby的一些库,但11章的命令行使用方式还是很有意义的, Cu...ber本身就是一个命令行工具,通过命令行,可以对特性文件进行一些过滤,对输出格式进行定制,以及集成到持续集成中。 命令行命令可以使用帮助:--help 一些重要的命令: --tags 过滤标签 --lines 指定行执行 xxx.feature:45 指定行的另一种形式 --format 格式化输出 如果真能把 Cucumber 用起来,用严肃的态度对待每一个步骤,以测试驱动开发,做出来的项目质量应该能大上一个台阶的,是个很好的工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值