BDD敏捷开发入门与实战


1.BDD的来由

2003年,Dan North首先提出了BDD的概念,并在随后开发出了JBehave框架。在Dan North博客上介绍BDD的文章中,说到了BDD的想法是从何而来。简略了解一下BDD的历史和背景,有助于我们更好地理解。

1.1 TDD的困惑

Dan在使用TDD敏捷实践时,时常会有很多同样的困惑萦绕脑海,这也是很多程序员敏捷实践都想知道的:

  • where to start
  • what to test
  • what not to test
  • how much to test in one go
  • what to call their tests
  • how to understand why a test fails

1.2 同事的小框架

当Dan用上了一位同事编写的小框架agiledox时,灵感闪现!这个框架其实很简单,它基于JUnit测试框架,根据测试类名和方法名,将每个测试方法都打印为类似文档的输出。程序员们意识到这个小玩具可以帮它们做一些文档性的工作,于是就开始用商业领域语法命名他们的类和方法,让agiledox产生的输出能直接被商业客户、分析师、测试人员都看懂!

// CustomerLookup
// - finds customer by id
// - fails for duplicate customers
// - ...
public class CustomerLookupTest extends TestCase {testFindsCustomerById() {...}testFailsForDuplicateCustomers() {...}...
}

1.3 “Ubiquitous Language”

此时,恰逢Eric Evans发表了畅销书DDD(领域驱动设计),其中描述了为系统建模时,使用一种基于商业领域模型的Ubiquitous Language,让业务词汇渗透到代码中。于是,Dan决定定义一种分析师、测试人员、开发者、业务人员、用户都能懂的”Ubiquitous Language”

Feature: <description>As a <role>I want <feature>So that <business value>Scenario: <description>Given <some initial context>,When <an event occurs>,Then <ensure some outcomes>.

就这样,BDD的雏形就出现了!但这种类似BRD的文档是如何与我们程序员的代码结合到一起的呢?下一节我们就详细分析一下。


2.三个核心概念

Feature、Scenario、Steps是BDD的三个核心概念,体现了BDD的三个重要价值:

  • Living Document
  • Executable Specification by Example(SbE)
  • Automated Tests

2.1 Feature

Feature就像是文档一样,描述了功能特性、角色、以及 最重要的商业价值

2.2 Scenario

场景就是上面提到的规范Specification。Cucumber提供了Scenario、Scenario Outline两种形式。使用时要注意,在Cucumber官博上的一篇文章“Are you doing BDD? Or are you just using Cucumber?”给出了一个反模式。

Scenario Outline: Detect agent type based on contract number (single contract found)Given I am on the "Find me" pageAnd I have entered a contract numberWhen I click the "Continue" buttonAnd a contract number match is foundAnd the agent type is <DistributorType>Then the contract number field will become uneditableAnd the "Back" button will be displayedAnd the following <text> and <input field type> will be displayedExamples:| DistributorType | input field type | text                            || Broker          | Date of birth    | Please enter your last name     || TiedAgent       | Last name        | Please enter your date of birth |

看出来了差别吧:Scenario Outline的核心依然应该是商业规则,而不能因为它对输入和输出的细化就将重点转移到UI界面

Scenario: Customer has a broker policy so DOB is requestedGiven I have a "Broker" policyWhen I submit my policy numberThen I should be asked for my date of birthScenario: Customer has a tied agent policy so last name is requestedGiven I have a "TiedAgent" policyWhen I submit my policy numberThen I should be asked for my last name

2.3 Steps

Steps就是实际编码了,我们要在Java中实现出Feature文件中各种场景对应的代码,让它变成“活文档”!


3.实战(上):分布式集群构建

之所以选择这么一个例子来实战,是因为网上的大部分例子都很简单而且雷同。通过这个例子,也是想试验一下BDD对于“业务性”不强的而且还是分布式的系统(即基础设施或中间件)是否也能发挥作用。这次实战也是一次比较奇妙的经历,不少核心类、接口和关于系统设计的想法都在这个过程中自然涌现~

3.1 开发环境

IDE当然还是选择Intellij,并且开启Cucumber插件,因为本实例是基于Cucumber实现的(其实其他的框架如JBehave都非常类似)。然后新建Maven工程,引入以下依赖包:

    <dependencies><dependency><groupId>info.cukes</groupId><artifactId>cucumber-java</artifactId><version>1.2.4</version><scope>test</scope></dependency><dependency><groupId>info.cukes</groupId><artifactId>cucumber-junit</artifactId><version>1.2.4</version><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency></dependencies>

3.2 编写feature文件

Feature相对比较好写,简单描述一下功能特性就行了。比如下面的集群自动创建功能:为了自动创建集群(功能),作为用户(角色),我想结点能自动互相发现形成集群以节省手工的工作量和时间(商业价值)。

Feature: Auto Cluster CreationIn order to create a cluster automaticallyAs a userI want the nodes can discover each other on their own

我们还需要一个启动类:

@RunWith(Cucumber.class)
@CucumberOptions(plugin={"pretty"}, features="src/test/resources", tags = {})
public class NodeDiscoveryStory {}

3.3 选择典型场景

为了简化,我只选了一个最简单的两结点集群建立的场景。首先结点1形成集群A,当结点2加入集群A后,集群中应有两个结点1和2。

Scenario: create a clusterGiven node-1 in cluster-A startsWhen a new node-2 in cluster-A startsThen cluster-A should have node: 1,2

场景的选择和编写至关重要,本例的实践过程中就碰到了一些问题,下面做一点个人的经验总结:

  • Given和When不要混淆:一个是环境上下文,一个是触发条件,例如”a cluster is running”和”a new node starts”。弄混的结果就是在场景1里的Given在2里又原封不动的变成When了。
  • 场景是可验证的不能含糊:这一点上与Feature不一样。一开始我描述的场景就比较模糊不清,例如”Then the cluster can acknowledge the new node”,这种描述不够精确,不好验证对错。实际上仔细想想,BDD对应设计的高层次与行为结果的可验证是不矛盾的
  • 只选几个典型场景:在BDD中千万不要追求覆盖率和细粒度,否则就将丧失BDD对业务逻辑的表现力!在Feature文件里只描述最核心的东西,把覆盖率这种只有我们程序员和QA关心的东西隐藏起来,在更细粒度的Case中去完成。

此外,还有关于Given和When是否要细分出一些And条件,比如本例中的Given和When就都可以分别拆成createNode和createOrJoinCluster两步,但这样的话会导致成员变量增多而显得比较乱,因为Cucumber中的Given和And、When和And之间是不能携带过去对象的。所以从下一部分的编码实现中能看出,最终我还是没有拆的那么细。

3.4 Steps编码实现

编码实现是最痛苦也最有收获的!一开始时一无所有的茫然,不断重构最终终于找到比较合理的设计。注意:代码不要跟着场景的描述走,比如变量cluster起名为clusterA,那就限定死了!我们的Steps应该是通用的,这里的Given、When都是可能用于其他场景的。

首先在@Given中启动一个Cluster加入一个Node,之后在@When中模拟在另一台机器上启动一个Node加入到集群的过程。因为实际上这个过程是在远程完成的,所以不能直接使用成员变量cluster。最后验证cluster中的结点列表,看是否已经包含两个结点。

public class MyStepdefs {private Cluster cluster;@Given("^node-(\\w+) in cluster-(\\w+) starts$")public void runCluster(String nodeId, String clusterName) {Node node = new Node(nodeId);cluster = new Cluster(clusterName, new CoordinatorMock());node.join(cluster);}@When("^a new node-(\\w+) in cluster-(\\w+) starts$")public void startNewNodeToJoinCluster(String nodeId, String clusterName) {Node newNode = new Node(nodeId);Cluster clusterSlave = new Cluster(clusterName, new CoordinatorMock());newNode.join(clusterSlave);}@Then("^cluster-(\\w+) should have node: (.+)$")public void joinCluster(String clusterName, List<String> nodeIds) {Assert.assertEquals(clusterName, cluster.getName());List<String> actualNodeIds = new ArrayList<String>();for (Node node : cluster.getNodes()) {actualNodeIds.add(node.getId());}Collections.sort(actualNodeIds);Assert.assertEquals(nodeIds, actualNodeIds);}}

对比下面典型的单元测试代码能够看出,BDD的Steps代码因为对应着Scenario,所以步骤分的比较清楚。而在普通Test Case中,Case中就会堆砌着类似@Given、@When、@Then的代码,并且每个Case都会有类似的代码。所以一般我们会提取出一些公关的代码,以使Case更为清晰,但BDD则直接更进一步。

    @Testpublic void testCachePut2_List() throws Exception {CacheResult<Object> ret = redis.cachePut(CACHE_NAME,Arrays.asList(new Person(1, 49, "alan"),new Person(2, 34, "hank"),new Person(3, 38, "carter")));Assert.assertTrue(ret.isOK());List persons = redis.cacheGetAll(CACHE_NAME, Arrays.asList(1, 3)).getValue();Collections.sort(persons);Assert.assertNotNull(persons);Assert.assertEquals(2, persons.size());...}

4.实战(下):核心类进化

下面就说一下通过这次BDD历险得到的核心类,以及是如何思考出来的。这个重构、思考、最终浮现出来的过程其实是最重要的!

最先映入脑海的就是Cluster和Node,其实Node也可以暂时用一个ID代替,之后有需要时再抽象成类,这里有些“着急”了直接新建了个Node类。

public class Cluster {private final String name;private List<Node> nodes = new ArrayList<Node>();public Cluster(String name) {this.name = name;}public void addNode(Node node) {}public String getName() {return name;}public List<Node> getNodes() {return nodes;}
}public class Node {private String nodeId;public Node(String nodeId) {this.nodeId = nodeId;}public void join(Cluster cluster) {cluster.addNode(this);}public String getId() {return nodeId;}
}

写好了@Given、@When、@Then之后,就可以跑起来Cucumber试试了,肯定是报错的。现在自然就有疑问了,@Then中的断言如何能够成功呢?所以Cluster背后需要一个能够帮助分布式通信的组件,于是就加上Coordinator接口。同时,我们创建一个Mock实现,利用static静态变量模拟网络通信的过程。

public interface Coordinator {void register(Cluster cluster);boolean addNode(Node node);}public class CoordinatorMock implements Coordinator {/** Simulate network communication */private static List<Cluster> clusters = new ArrayList<Cluster>();@Overridepublic void register(Cluster cluster) {clusters.add(cluster);}@Overridepublic boolean addNode(Node node) {for (Cluster cluster : clusters) {cluster.handleAddNode(node);}return true;}
}

最后让Cluster注册到Coordinator上,调用addNode()接口模拟分布式通信,并添加handleAddNode()处理请求就可以了!这样我们就完成了BDD的一个简单实例!

public class Cluster {private final String name;private final Coordinator coordinator;private List<Node> nodes = new ArrayList<Node>();public Cluster(String name, Coordinator coordinator) {this.name = name;this.coordinator = coordinator;coordinator.register(this);}public void addNode(Node node) {coordinator.addNode(node);}public void handleAddNode(Node node) {nodes.add(node);}public String getName() {return name;}public List<Node> getNodes() {return nodes;}
}

5.总结

每种新事物的产生都不可避免地会伴随着各种各样的解读,毕竟每个人都有自己的看法和理解。有的理解深刻直达本质,有的独辟蹊径另立门派,也有的是偏见和误解。BDD也一样,可能会人被当做跟TDD一样的东西,也可能会被看做测试的一种。

通过本文的介绍,大家应该能看到BDD的闪光点。它提升了TDD的粒度和抽象层次,并以统一而规范的语言作为文档,消除了软件开发中各种人员的沟通障碍。同时以实用的框架将文档与代码粘合到一起,使文档可执行化、代码文档化。

转载于:https://www.cnblogs.com/xiaomaohai/p/6157589.html

BDD敏捷开发入门与实战相关推荐

  1. 敏捷开发与 DevOps 实战

    点击观看大咖分享 随着互联网.移动互联网的浪潮,软件工程从瀑布到敏捷发生了巨大的变化,服务器架构也从 IOE 演变到微型机,又发展为云计算,运维成本越来越低,持续部署逐渐流行起来.本课程主要讲解敏捷开 ...

  2. 《Android 开发入门与实战(第二版)》——6.6节配置改变

    本节书摘来自异步社区<Android 开发入门与实战(第二版)>一书中的第6章,第6.6节配置改变,作者eoe移动开发者社区 组编 , 姚尚朗 , 靳岩,更多章节内容可以访问云栖社区&qu ...

  3. android开发入门与实践_我的新书《Android App开发入门与实战》已经出版

    前言 工作之余喜欢在CSDN平台上写一些技术文章,算下时间也有两三年了.写文章的目的一方面是自己对技术的总结,另一方面也是将平时遇到的问题和解决方案与大家分享,还有就是在这个平台上能和大家共同交流. ...

  4. 极客学院腾讯 TAPD·极客开放日 [敏捷开发畅想与实战]

    极客学院&腾讯 TAPD -极客开放日活动,本次主题「敏捷开发畅想与实战」. 多位行业大佬亲临:嘉宾包括中国 IT 界著名意见领袖.敏捷开发布道师熊节先生,源自硅谷.扎根香港的行业独角兽 Af ...

  5. Android自定义控件开发入门与实战(1)绘图基础

    今天从leader那里拿到了启舰大神写的<自定义控件开发入门与实战>这本书,据说看完了,至少写起自定义view也不会慌. 最重要的是多练,所以这本书基本设计到的我没有涉及过的控件开发(之前 ...

  6. Google Android开发入门与实战

    Google Android开发入门与实战 [作 者]靳岩;姚尚朗 [同作者作品] [作译者介绍]  [出 版 社] 人民邮电出版社     [书 号] 9787115209306  [上架时间] 2 ...

  7. 《Android 开发入门与实战(第二版)》——导读

    本节书摘来自异步社区<Android 开发入门与实战(第二版)>一书中的目录,作者eoe移动开发者社区 组编 , 姚尚朗 , 靳岩,更多章节内容可以访问云栖社区"异步社区&quo ...

  8. 《Google Android 开发入门与实战》

    <Google Android 开发入门与实战>(含1张DVD光盘) 市 场 价:¥55 书 号:9787115209306 出版日期:2009 年6月 开 本:16开 页码:340 [内 ...

  9. Android开发入门与实战之Android应用安装卸载

    当一个Android开发者完整的开发完毕一个程序应用软件结束后要进行软件测试,这就是软件测试员的来历,那么在这之前,要进行Android开发应用的安装与卸载. Android开发入门与实战之Andro ...

最新文章

  1. 这样调优:让你的 IDEA 快到飞起来,效率真高!
  2. 表的基本查询(数据库篇)
  3. SpringBoot学习笔记(4)----SpringBoot中freemarker、thymeleaf的使用
  4. SAP Spartacus Enable the unit to allow editing 的信息提示框实现
  5. 用 State Pattern 来实现一个简单的 状态机
  6. 负边距在布局中的使用
  7. 深入理解ob_flush和flush的区别(转)
  8. C++引用计数(reference counting)技术简介(2)
  9. 【问题解决】c.a.c.n.c.NacosPropertySourceBuilder : parse data from Nacos error,dataId:xxxxxx.yml
  10. 《社会调查数据管理——基于Stata 14管理CGSS数据》一第1章 导言1.1 数据管理不被重视...
  11. DSP入门必看(非常好的DSP扫盲文章)
  12. vagrant设置磁盘大小
  13. 基于springboot的学生管理系统
  14. 《创新者的基因》读书笔记
  15. 字符编码 - GB2312简体中文编码表
  16. Dell商用台式机、笔记本、服务器800电话
  17. 华为服务器检索信息,裸金属服务器使用标签检索资源
  18. Github博客+腾讯云域名的快捷绑定方案
  19. 微信上如何开直播?直播的方法
  20. 如何使用WordPress插件添加约会预约日历

热门文章

  1. EMNLP 2021-多模态Transformer真的多模态了吗?论多模态Transformer对跨模态的影响
  2. CV初级研究工程师,苏黎世联邦理工学院招聘
  3. 开源代码准确率99%+,人脸识别问题真的被解决了吗?
  4. 北大阿里中科院提出细粒度人体姿态迁移方法,提升外观细节逼真度
  5. GridMask:SOTA 数据增广方法,显著改进分类、检测、分割效果
  6. 韩国ETRI提出实时Anchor-Free实例分割算法CenterMask,代码将开源
  7. Batch Normalization的一些缺陷
  8. 公式太多,读不懂? 一文带你领略KNN近邻算法~简单易懂
  9. 全局路径规划:图搜索算法介绍1(BFS/DFS)
  10. c语言中do有什么作用,C语言中do while语句的用法是什么?_后端开发