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

2023-11-06

GitChat 作者:冰尘
原文:行为驱动开发(BDD)你准备好了吗?
关注微信公众号:「GitChat 技术杂谈」 一本正经的讲技术

【不要错过文末彩蛋】

enter image description here

这个Chat笔者将会和大家一起探讨下面的主题:

  1. 什么是行为驱动开发(BDD)?

  2. 为什么使用行为驱动开发(BDD)?

  3. 如何做行为驱动开发(BDD)?

  4. 遗留系统适合使用行为驱动开发(BDD)吗?

  5. 总结

一、什么是行为驱动开发(BDD)?

BDD,“Behavior Driven Development”的缩写,中文意思,行为驱动开发,BDD本质上是一种敏捷软件开发实践,它鼓励软件项目中的开发者、测试,用户,业务分析人员等之间相互协作。BDD最初是由Dan North在2003年命名,它包括验收测试和客户测试驱动等极限编程实践,是作为对测试驱动开发(TDD,Test drive development)的回应。在过去数年里,它得到了很大的发展。

为了加深大家对BDD概念的理解,咱们来看看《BDD in action》一书对BDD的定义和概括,

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提供了一种通用的,简单的,结构化的描述语言,这种语言既可以是英语也可以是其他本地的语言,通过他能够很方便让项目成员和业务干系人非常顺畅的沟通需求,及时这些干系人不懂的任何编程语言。

下面举1个简单易懂的栗子: 计算器的例子。

假设我们需要开发一个计算器,其 里面有一个加法的运算,那应该如何描述,才能让所有参与项目的人都能看懂呢?下面就是其中的一种写法。

enter image description here

上面这个例子,写的就是描述一个计算器加法的一个例子,是不是非常直观易懂,上面的文件,其实叫Feature(特性文件)。那为什么容易看懂呢? 因为其使用了Gherkin语法。那么Gherkin是什么呢? 其实,Gherkin语法就是使用 Given,when,then等关键字词来描述一个用户故事(User Story)。形成一份不论是客户,业务分析人员,测试,还是开发,都能读懂的文件格式。具体定义和用法请大家参考这个链接

需要说明的是,请大家注意左边的红色的关键字,Feature,Scenario,Given,When,And,Then; 这些关键字其实就是Gherkin语法定义的标准关键字,其主要的关键字如下,

  • Feature

  • Background

  • Scenario

  • Given

  • When

  • Then

  • And

  • But

    • *
  • Scenario Outline

  • Examples

上面的关键字中有一个examples的关键字,这个关键字非常的好,不知道大家发现没有,人们在日常交流的过程中,有的时候,为了让大家对某一件比较复杂或者难以理解的,或者容易产生歧义的事情,喜欢举个例子,Gherkin也不例外,为了让大家在使用行为驱动开发的过程,相互协作的各个团队之间,更好的理解需求,举个例子是一个非常好的方式。下面咱们可以看一个使用Gherkin中的examples的例子,

故事上下文如下:

虽然现在已经进入了无现金交易的时代,但是我们有的时候还是需要去银行取点现金,那我们就看一个取钱的例子吧。

enter image description here

上面的一个基于BDD的特性文件(Feature)中,明显的用到了Examples关键字,举了3个例子(三行),比如第一行,当前账号余额为500美元,取了50美元,我们收到了50美元,账号还剩下450美元。

这个时候,可能有读者会问,如果我们公司是国内的公司,没有英语的环境,项目经理,客户,产品经理的英语都不太好,那我用英语写的特性文件(Feature),他们都能看懂吗?,是不是还要边看边查字典啊??? 哈哈,没有关系,Gherkin语法是支持国际化的,下面是他们的一个关键字对照表。

英文关键字 中文关键字
feature “功能”
background “背景”
scenario “场景”, “剧本”
scenario_outline “场景大纲”, “剧本大纲”
examples “例子”
given “假如”, “假设”, “假定”
when “当”
then “那么”
and “而且”, “并且”, “同时”
but “但是”

我们可以直接把中文写在Feature文件中,处理和解析就交给第三方的框架的吧,比如Cucumber。什么? Cucumber,是什么鬼东西,我查查字典,原来是“黄瓜”的意思。 其实,亲们, Cucumber就是实现行为驱动开发的一个开源框架而已,其中支持BDD的框架,除了Cucumber,还有Spec,Spock等等等开源项目,在后面分享到的如何实现BDD的章节中,我会为大家进一步介绍Cucumber。

二、为什么使用行为驱动开发(BDD)?

如果读者已经有过软件开发工作经验的话,应该能很快看懂传统需求挖掘,分发和使用流程,一般情况下,应该是下面的样子。

enter image description here

那么,通过这张图,我想您一定能立马发现,所有的需求流动和维护都是单方向的,而我们知道,软件的需求其实就是软件的目标,就是我们应该交付的产品,是我们应该要做的正确的事情。而对于用户的需求而言,有的时候其实是很复杂的,有的时候客户在提出某一想法的时候,其实压根自己也不知道最终需要一个什么产品,只是大概模糊的知道需要实现一个功能,而且客户的想法和最终实现这个产品的开发人员做出来的东西最终肯能会不太一样,因为开发人员可能已经开始根据最初的需求文档已经把代码实现了,QA也把测试用例写好了,但是QA根据需求文档写出的测试用例和开发人员开发出来的产品的可能根本匹配不上,好多的工作就这样白白浪费了,于是团队成员抱怨了。

另外,谁有能保证业务人员把需求文档写出来后,没有歪曲和误解商务人员告诉给他的需求和想法,开发人员能通过文档把业务分析人员写的东西全部理解透吗?有的时候业务需求文档,真的不是特别的有趣,没有例子,比较抽象,有歧义。怎么办?怎么办?怎么办?重要话说三遍,那有没有一种媒介,可以让大家及时的,基于同一个平台的交流,而且用于交流的媒介,对于需求的描述也非常的生动,会根据以后软件的行为进行分类,并提供一些生动的例子呢? 下面我们看看BDD会如何做。

enter image description here

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

  • 减少浪费

  • 节省成本

  • 容易并且安全的适应变化

  • 因为少了中间的转达环节,从而能够快速交付产品

三、如何做行为驱动开发(BDD)?

enter image description here

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

enter image description here

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

下面,笔者就以Cucumber和JUnit为例,举一个BDD的例子吧。大家对JUnit比较熟悉,但是对Cucumber可能会相对陌生一点,笔者就花一点笔墨,简单介绍了一下Cucumber。

Cucumer是一个实现了BDD的一个框架,其支持下面的语言和框架集成,
Cucumer简直要逆天了,基本上所有的主流语言都支持,而且还能和市面上一些流行框架相结合,比如自动化测试框架,Selenium; Ruby的超级牛逼的Web开发框架Ruby On Rails等等。

enter image description here

是不是感觉很强大啊!!!! 下面进入实战。咱们以Java代码为例子,
结合Cucumber,JUnit以及Maven给大家演示。

3.1 建立一个Maven项目并添加Cucumber依赖库

首先,我们建立一个Maven的项目,名字就叫BDDKata,为什么叫这个名字呢?

因为针对某一种特定技术或技能进行重复性的练习,从而将其熟练掌握,这在编程领域常被人称为“编码套路”(Code Kata)。Code Kata的概念是由David Thomas提出的,他是《程序员修炼之道:从小工到专家》的作者之一,大家如果感兴趣的话可以买这本书看看,非常经典的一本书。因为当前的例子也是针对BDD的一个简单的练习,所以我也取名就做BDDKata。

既然是基于Java Cucumber 的类库去实现BDD,那么我们首先要把Cucumber相关的jar通过Maven的依赖(Dependency)加入进来,

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.icedust.bdd</groupId>
  <artifactId>bddKata</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>bddKata</name>
  <description>bddKata</description>
  <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>
</project>

3.2 安装Cucumber Eclipse插件

为了支持Feature的Gherkin语法,我们需要在Eclipse开发环境里面安装下面的插件:

https://cucumber.io/cucumber-eclipse/update-site

具体安装方法,请到百度或者google搜索。

3.3 新建一个Feature文件,编一个需求

为了简单起见,我们选择一个买服装的一个场景,下面是根据和业务人员调研后,业务分析人员得到的一个服装店收银台的一个例子,

Feature: Checkout on Shopping
  Scenario Outline: Checkout Shirt 
    Given the price of a "Shirt" is 200RMB 
    When I checkout <count> "Shirt"
    Then the total price should be 400RMB

    Examples:
    | count | total   |     
    | 1     | 200     | 
    | 2     | 400     |

  Scenario: Two Shirt scanned separately 
    Given the price of a "Shirt" is 300RMB 
    When I checkout 1 "Shirt"
    And I checkout 1 "Shirt"
    Then the total price should be 600RMB

  Scenario: A Shirt and an Shoes
    Given the price of a "Shirt" is 200RMB
    And the price of a "Shoes" is 300RMB 
    When I checkout 1 "Shirt"
    And I checkout 1 "Shoes"
    Then the total price should be 500RMB

里面总共有3个场景(Scenario),

第1个场景:

假设一件衬衣的价格是200,买了 n件,其价格应该是多少钱?

第2个场景:

买了两件衬衣,总共是多少钱?

第3个场景:

买了一件衬衣和一双鞋是多少钱?

3.4 运行Feature文件,生成Cucumber的步骤(Steps)代码

当我们选中这个Feature文件(Checkout.feature)文件的时候,我们运行Cucumber Feature的时候,如下图。

enter image description here

其部分输出如下:

4 Scenarios (4 undefined)
15 Steps (15 undefined)
0m0.000s


You can implement missing steps with the snippets below:

@Given("^the price of a \"(.*?)\" is (\\d+)RMB$")
public void the_price_of_a_is_RMB(String arg1, int arg2) throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@When("^I checkout (\\d+) \"(.*?)\"$")
public void i_checkout(int arg1, String arg2) throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@Then("^the total price should be (\\d+)RMB$")
public void the_total_price_should_be_RMB(int arg1) throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

上面的代码提示我们有4个场景,15个步骤没有定义。

那么明明上面笔者说了只有3个场景,那为什么提示的时候,是4个场景呢?

原来,第一个场景描述里面有一个Examples,里面举了2个例子,在加上后面的2个所以是4个。15个
步骤,指的是Gherkin关键字所对应的语句(Given,When,And,Then 等等),注意带有Examples的第一个场景,因为有2个example,所以要乘以2。

根据提示,我们把胶水代码从上面的控制台输出拷贝下面,并新建一个java类:CheckoutSteps ,文件名字为CheckoutSteps.java,内容如下:

package com.icedust.bddkata;
import cucumber.api.java.en.*;
import cucumber.api.PendingException;

public class CheckoutSteps {
    @Given("^the price of a \"(.*?)\" is (\\d+)RMB$")
    public void the_price_of_a_is_RMB(String arg1, int arg2) throws Throwable {
        // Write code here that turns the phrase above into concrete actions
        throw new PendingException();
    }
    @When("^I checkout (\\d+) \"(.*?)\"$")
    public void i_checkout(int arg1, String arg2) throws Throwable {
        // Write code here that turns the phrase above into concrete actions
        throw new PendingException();
    }

    @Then("^the total price should be (\\d+)RMB$")
    public void the_total_price_should_be_RMB(int arg1) throws Throwable {
        // Write code here that turns the phrase above into concrete actions
        throw new PendingException();
    }

}

3.5 在步骤代码里面加上JUnit的断言并根据断言驱动业务实现

根据BDD的开发的原则,先写BDD的步骤代码(Steps)和单元测试代码,然后再写实现代码,因为实现代码还没有写,所以先写的骤代码(Steps)和单元测试代码肯定运行失败,但是没有关系,这个时候我们就可以写业务实现代码了,然后让单元测试通过,一旦单元测试通过,我们就可以对代码进行重构,上面的步骤简称RGB(红绿蓝),具体含义大家请参考这篇文章

3.5.1 修改步骤代码并编写单元测试

在根据Feature文件生成的步骤代码中(Steps)中,写上业务实现的类的对象和以及其方法,通过Cucumber中定义的Steps(带有When,Given,then , And的关键字),获取Feature文件里面的数据,最后写上单元测试的断言,具体代码如下

enter image description here

注意: 因为业务实现代码暂时还没有实现,比如Checkout类,以及Checkout类的中add() 方法,所以Eclipse开发环境出现编译异常,不要紧,因为这是BDD&TDD的一个必经的步骤。

3.5.2 编写业务实现

下面咱们通BDD的测试用例来驱动业务代码的开发,驱动出来的业务代码如下:

package com.icedust.bddkata;
public class Checkout {
    private int runningTotal = 0;

    public void add(int count, int price) { 
        runningTotal += (count * price);
    }

    public int total() { 
        return runningTotal;
    }
}

3.5.3 重新运行测试

这个时候,我们发现,CheckoutSteps类里面的异常消失了。

那么如何自动运行单元CheckoutSteps定义的Step以及其中的单元测试呢?

这个时候,我就需要加入一个启动BDD的Step和单元测试的入口类: RunBDDTest,如下所示意。

package com.icedust.bddkata;

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 RunBDDTest {

}

3.5.4 运行测试类:RunBDDTest 并输出结果

选中RunBDDTest的类并运行单元测试,则在控制台会出现下面的输出。

从最后一句话我们可以得知,4个场景,15个步骤都运行成功了。

Feature: Checkout on Shopping

  Scenario Outline: Checkout Shirt            [90m# com/icedust/bddkata/checkout.feature:3[0m
    [36mGiven [0m[36mthe price of a "Shirt" is 200RMB[0m
    [36mWhen [0m[36mI checkout <count> "Shirt"[0m
    [36mThen [0m[36mthe total price should be <total>RMB[0m

    Examples: 

  Scenario Outline: Checkout Shirt         [90m# com/icedust/bddkata/checkout.feature:10[0m
    [32mGiven [0m[32mthe price of a "[0m[32m[1mShirt[0m[32m" is [0m[32m[1m200[0m[32mRMB[0m [90m# CheckoutSteps.the_price_of_a_is_RMB(String,int)[0m
    [32mWhen [0m[32mI checkout [0m[32m[1m1[0m[32m "[0m[32m[1mShirt[0m[32m"[0m              [90m# CheckoutSteps.i_checkout(int,String)[0m
    [32mThen [0m[32mthe total price should be [0m[32m[1m200[0m[32mRMB[0m  [90m# CheckoutSteps.the_total_price_should_be_RMB(int)[0m

  Scenario Outline: Checkout Shirt         [90m# com/icedust/bddkata/checkout.feature:11[0m
    [32mGiven [0m[32mthe price of a "[0m[32m[1mShirt[0m[32m" is [0m[32m[1m200[0m[32mRMB[0m [90m# CheckoutSteps.the_price_of_a_is_RMB(String,int)[0m
    [32mWhen [0m[32mI checkout [0m[32m[1m2[0m[32m "[0m[32m[1mShirt[0m[32m"[0m              [90m# CheckoutSteps.i_checkout(int,String)[0m
    [32mThen [0m[32mthe total price should be [0m[32m[1m400[0m[32mRMB[0m  [90m# CheckoutSteps.the_total_price_should_be_RMB(int)[0m

  Scenario: Two Shirt scanned separately   [90m# com/icedust/bddkata/checkout.feature:13[0m
    [32mGiven [0m[32mthe price of a "[0m[32m[1mShirt[0m[32m" is [0m[32m[1m300[0m[32mRMB[0m [90m# CheckoutSteps.the_price_of_a_is_RMB(String,int)[0m
    [32mWhen [0m[32mI checkout [0m[32m[1m1[0m[32m "[0m[32m[1mShirt[0m[32m"[0m              [90m# CheckoutSteps.i_checkout(int,String)[0m
    [32mAnd [0m[32mI checkout [0m[32m[1m1[0m[32m "[0m[32m[1mShirt[0m[32m"[0m               [90m# CheckoutSteps.i_checkout(int,String)[0m
    [32mThen [0m[32mthe total price should be [0m[32m[1m600[0m[32mRMB[0m  [90m# CheckoutSteps.the_total_price_should_be_RMB(int)[0m

  Scenario: A Shirt and an Shoes           [90m# com/icedust/bddkata/checkout.feature:19[0m
    [32mGiven [0m[32mthe price of a "[0m[32m[1mShirt[0m[32m" is [0m[32m[1m200[0m[32mRMB[0m [90m# CheckoutSteps.the_price_of_a_is_RMB(String,int)[0m
    [32mAnd [0m[32mthe price of a "[0m[32m[1mShoes[0m[32m" is [0m[32m[1m300[0m[32mRMB[0m   [90m# CheckoutSteps.the_price_of_a_is_RMB(String,int)[0m
    [32mWhen [0m[32mI checkout [0m[32m[1m1[0m[32m "[0m[32m[1mShirt[0m[32m"[0m              [90m# CheckoutSteps.i_checkout(int,String)[0m
    [32mAnd [0m[32mI checkout [0m[32m[1m1[0m[32m "[0m[32m[1mShoes[0m[32m"[0m               [90m# CheckoutSteps.i_checkout(int,String)[0m
    [32mThen [0m[32mthe total price should be [0m[32m[1m500[0m[32mRMB[0m  [90m# CheckoutSteps.the_total_price_should_be_RMB(int)[0m

4 Scenarios ([32m4 passed[0m)
15 Steps ([32m15 passed[0m)
0m0.099s

3.5.5 重构代码

运行成功后,可以重构代码,重构的代码不仅仅只包括重构业务实现代码,还包括重构BDD的测试代码。重构代码,100个人可能就有100种重构的方式,具体如何重构代码,这又是另外一个话题,读者可以看Martin Fowler 著写的《重构 改善既有代码的设计》以及Robert C. Martin写的《代码整洁之道 程序员的职业素养》,笔者就不在赘述。感兴趣的读者,可以自行重构。

上面的整个流程其实可以用下面的2张图,完美描述。

enter image description here

上面的例子,列举的是一个新系统的中使用BDD的例子,那么对于遗留系统该如何BDD呢?

四、复杂遗留系统适合使用行为驱动开发(BDD)吗?

笔者去年有一段时间,特别针对了这个问题进行了研究,因为遗留系统,一般年限很长,用到的技术多种多样。而且有的部分代码有单元测试,有的部分的代码没有单元测试。有的单元测试,一看就知道不是先写测试再写业务实现,而是先写业务实现,再写单元测试的,且单元测试都是补上去的。

针对这种情况,笔者认为,如果要把一个大型的,维护了10几年的遗留项目,彻底推翻,使用 BDD和TDD进行重写是不现实的,而且随着复杂性的提高,其成本也是很高的。除非有特别的理由,否则很难得到领导的同意。那这个时候,我们该怎么办?难道BDD在复杂的遗留系统里面就用不上了吗?

其实,非也,冰冻三尺,非一日之寒,下面是笔者的一些个人建议,仅供参考,不喜勿喷。

首先要给大家灌输BDD和TDD的好处。教会大家如何做BDD和TDD。因为在遗留系统中有的时候,会添加一些新的特性,这个时候,可以在不动别的功能特性的基础上,尝试对新添加的功能使用BDD和TDD。

根据笔者观察,因为复杂的遗留系统,很多代码很难测试,而且还有很多私有方法,静态方法,有final修饰的,很难写单元测试的。这个时候,其实瓶颈就在如何Mock这些复杂的上下文场景。推荐大家使用PowerMock。

如果有时间的话,先从重构现有的单元测试的用例开始,只要单元测试的用例好维护,好扩展了,大家才能有动力和心情,继续写更多更好的单元测试,从而培养大家先写单元测试的感觉。

如果您有更好的建议和方案,也可以在笔者的这片文章下面留言,笔者将会把其加入到本文中来。

五、总结

感谢大家看完了本文,通过本文,笔者给大家分享了什么是BDD,为什么要做BDD,如何来做BDD,最后探讨了如何做BDD已经在复杂的遗留系统上应该使用什么策略来做BDD。 其实,我们知道在软件开发或者维护过程中,基本的主流角色有,开发,测试,客户,用户,项目经理,运维人员等。 而在生产和开发一个软件的过程中,处处充满风险,这个时候,从宏观角度来说,做正确的事是最重要的;从微观角度来说,正确的做事也很重要。其中,做正确的事是最最重要的,如果大家看过玩过传递猜词游戏的话,应该知道,第一个人看到一个正确的词语,然后用动作表达出来,然第二个人猜意思,然后再用动作表达给第三个人。。。。。 往往到了最后,意思可能大相径庭。

enter image description here

在软件开发的需求分析和实现阶段又何尝不是这样呢?本来客户需要的是一辆自行车,结果却可能得到一辆摩托车。

enter image description here

这一切的一切都是沟通惹的祸,其实就是没有做正确且被期望的事情。而BDD(行为驱动开发), 就是为了解决这个问题。我们知道,BDD就是先写需求和功能点描述,这种描述客户,经理,开发,测试都能看懂,然后根据这些特性文件,列出一个个生动形象的场景,即使没有写过任何代码的人都能看懂,而且是基于文本的。开发人员把这些特性文件转换成具体的测试用例并驱动业务实现,目的说白了,就是让开发出来的系统正是客户所需要的,从而保证了做正确的事情,正如上面的图所示,客户需要的是自行车,就真正开发出来自行车,而不是开发出摩托车。

参考资料

《BDD In Action》

《The Cucumber for Java Book(Pragmatic,2015)》

【GitChat达人课】

  1. 前端恶棍 · 大漠穷秋 :《Angular 初学者快速上手教程
  2. Python 中文社区联合创始人 · Zoom.Quiet :《GitQ: GitHub 入味儿
  3. 前端颜值担当 · 余博伦:《如何从零学习 React 技术栈
  4. GA 最早期使用者 · GordonChoi:《GA 电商数据分析实践课
  5. 技术总监及合伙人 · 杨彪:《Gradle 从入门到实战
  6. 混元霹雳手 · 江湖前端:《Vue 组件通信全揭秘
  7. 知名互联网公司安卓工程师 · 张拭心:《安卓工程师跳槽面试全指南

这里写图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

行为驱动开发(BDD)你准备好了吗? 的相关文章

  • 第一篇博--初入CSDN

    选择开博并计划按月定期发布一些敲码路上的收获和心得 目的是在梳理知识 复盘总结的同时 能够和志同道合的朋友们一起学习 共同进步 在互联网上留下一份自己的痕迹 与诸君共勉 联系方式 631435743 qq com 欢迎大家找我讨论计算机专业
  • IDEA去除掉虚线,波浪线,和下划线实线的方法

    推荐一下个人的公众号 终码一生 专注于Java技术学习 开源项目分享和常见问题解决等 喜欢的小伙伴可以关注下 感谢大家的支持 初次安装使用IDEA 总是能看到导入代码后 出现很多的波浪线 下划线和虚线 这是IDEA给我们的一些提示和警告 但
  • 工业安全生产信息化平台的基本架构和关键功能分享

    工业安全生产信息化平台是指利用信息技术手段 将工业安全生产管理与数据采集 传输 处理相结合 实现对工业安全生产全过程的数字化 信息化 智能化管理的平台 它通过集成多种信息系统和设备 实现对重大危险源监控预警 安全风险分级管控 安全生产一张图
  • 【Android Studio】Design editor is unavailable until next gradle sync.如何解决?

    Design editor is unavailable until next gradle sync 如何解决 打开一个网上开源的下载文件 在查看安卓页面布局 也就是控件布局时 会出现一个bug Design editor is unav
  • Windows驱动开发(一)第一个驱动程序

    首先我们需要了解 在操作系统中 是分两种权限的 一种是内核态 我们也称为0环 一种是用户态 称之为3环 而在我们的电脑中 驱动程序是运行在内核态的 这意味着和操作系统内核是在同一权限的 而普通的应用程序的权限是最低的 高权限谁不想拥有呢 因
  • 【电机学】直流电机

    直流电机 什么是直流电机 直流电机的工作原理 直流发电机的工作原理 直流电动机的工作原理 可逆性原理 直流电机的主要结构部件 直流电机的电枢绕组 基本特点 并联支路对数 电刷的放置 一些概念 直流电机的磁场 直流电机的空载磁场 电枢电流Ia
  • PTP/IP协议

    PTP IP PTP over IP 是一个通过IP连接 建立在 Picture Transfer Protocol PTP 上的传输层 我之所以在了解这个东西是因为有一台 Nikon 相机支持 WLAN 和手机传输相片 但是APP设计得极
  • App隐私监管新规实施 隐私合规检测要注意这几点?

    5月1日 国家四部委联合制定的 常见类型移动互联网应用程序必要个人信息范围规定 简称 规定 将正式实施 规定 明确移动互联网应用程序 App 运营者不得因用户不同意收集非必要个人信息 而拒绝用户使用App基本功能服务 要求各地各相关单位督促
  • GitHub Action入门简介

    1 What is GitHub Actions GItHub Actions是一个持续集成和持续交付的平台 能够让你自动化你的编译 测试和部署流程 GitHub 提供 Linux Windows 和 macOS 虚拟机来运行您的工作流程
  • 销售人员一定要知道的6种获取电话号码的方法

    对于销售来说 电话销售是必须要知道的销售方法 也是销售生涯中的必经之路 最开始我们并不清楚这么电话是从哪里来的 也不清楚是通过哪些方法渠道获取 那么今天就来分享给各位销售人员获取客户电话号码的方法 1 打印自己的名片 在工作当中少不了接触其
  • C#与Java的不同

    https www cnblogs com Yan3399 p 17324904 html 1 C 对应java中关键字 base super this this sealed final 1 base关键字 对应java super C
  • CleanMyMac X2024值不值得下载?

    macOS已经成为最受欢迎的桌面操作系统之一 它提供了直观 简洁的用户界面 使用户可以轻松使用和管理系统 macOS拥有丰富的应用程序生态系统 还可以与其他苹果产品和服务紧密协作 如iPhone iPad 用户可以通过iCloud同步和共享
  • MacBook电脑内存容量小根本不够用?如何一键解决?

    得益于M1系列芯片的强势表现 很多朋友都换用了MacBook 首次接触到了macOS系统 但出乎意料的是 很多人就开始受罪了 明明这么出色的硬件 为何到处都不顺手呢 尤其是容量 MacBook相比同价位的Windows笔记本 硬盘本来就偏小
  • fl studio20中文内测版下载2024最新完美实现汉化

    fl studio20是一款众所周知的水果编曲软件 能够剪辑 混音 录音 它的矢量界面能更好用在4K 5K甚至8K显示器上 还可以可以编曲 剪辑 录音 混音 让你的计算机成为全功能录音室 不论是在功能上面还是用户界面上都是数一数二的 但该软
  • 走进暄桐教室 一起观看暄桐同学作品及感受

    暄桐是一间传统美学教育教室 创办于2011年 林曦是创办人和授课老师 教授以书法为主的传统文化和技艺 旨在以书法为起点 亲近中国传统之美 以实践和所得 滋养当下生活 其实 暄桐教室的写字画画课 不仅是林曦老师单方面的输出 而是有分享 也有回
  • linux使用文件描述符0、1和2来处理输入和输出

    文件描述符012 在Linux中 文件描述符0 1和2分别代表标准输入 stdin 标准输出 stdout 和标准错误 stderr 它们用于处理进程的输入和输出 文件描述符0 stdin 文件描述符0是进程的标准输入 通常用于读取用户的输
  • FAM amine, 6-isomer,1313393-44-0,含有纯6-异构体的荧光团,6-FAM NH2

    产品名称 FAM amine 6 isomer 6 FAM NH2 中文名称 6 羧基荧光素 氨基 CAS 1313393 44 0 分子式 C27H26N2O6 分子量 474 51 纯度 95 结构式 产品描述 荧光素衍生物具有胺基 含
  • linux ARM64 处理器内存屏障

    一 内存类型 ARMv8架构将系统中所有的内存 按照它们的特性 划分成两种 即普通内存和设备内存 并且它们是互斥的 也就是说系统中的某段内存要么是普通内存 要么是设备内存 不能都是 1 普通内存 Normal Memory 普通内存的特性是
  • 对技术行业的深度思考

    技术行业是当今世界最为热门和发展迅猛的领域之一 无论是互联网 人工智能还是区块链 技术的快速发展正在改变着我们的生活和社会 然而 我们是否真正思考过技术在我们生活中的影响和意义 本文将对技术行业展开深度思考 探讨其带来的优势与挑战 以及如何
  • 液晶偏振光栅

    1 偏振 光是横波 在垂直于光的传播方向的平面内光波振动 即E矢量振动 各方向振幅都相等的光为自然光 只在某一方向有光振动的光称为线偏振光 各方向光振动都有 但振幅不同的光叫部分偏振光 螺旋着振动的光称圆偏振光 分旋和右旋 2 庞加莱球表示

随机推荐

  • Shell脚本攻略:文本三剑客之sed

    目录 一 理论 1 sed 二 实验 1 sed命令的寻址打印 2 显示奇偶 3 查找替换 4 后向引用 5 截取版本号 6 替换IP地址 一 理论 1 sed 1 概念 sed 英文全称为stream editor流式编辑器 sed 对输
  • neo4j学习笔记

    文章目录 neo4j note 一 概述 1 链接 2 介绍 数据模型 二 使用 1 环境搭建 2 CQL 1 创建 Create 2 查询Match 3 Return 4 关系基础 创建 1 现有节点之间创建无属性的关系 2 现有节点之间
  • 哈夫曼树构造哈夫曼编码

    在传输文字时 经常要将文字转换成二进制字符串 所以我们希望编码最短 但是又想保证它的唯一性 哈夫曼树具有最小带权路径长度 用来实现编码就可以编码最短 所以用哈夫曼树来构造编码 而前缀编码就可以保证在解码的时候不会出现多种可能 就实现了唯一性
  • 第二章。c#变量和数据输入

    1 C 中常见的数据类型 1 整型 整数类型 表示整数 比如年 月 日 年龄等都是整数 整型的关键字 int 最常用的 short long 2浮点型 带小数点的数 比如身高 米 体重 100 5kg 等都是浮点数 浮点型分成两种 1 单精
  • quartz对于定时任务Misfire的处理

    使用quartz过程中 产生了很多问题 遇到就记录一下 虽然用的比较少了 但还是有一些项目在使用 问题描述 创建一个每天执行的任务test1 创建自动运行状态 然后停止任务 一直等到当天定时时间过去 然后再启动 发现定时任务还是先执行了一次
  • 在Bios中开启虚拟化设置

    1 进入BIOS 开机时按baiF2或F12或DEL或ESC等键 各电脑有所不同 2 进入duBIOS后 找到Configuration选项 选zhi择Intel Virtual Technology并回车 将光标dao移至Enabled
  • C++使用模板实现元素的反序

    实现任意类型序列中元素的反序 所涉知识点 示例代码 开发环境 运行结果 注意 所涉知识点 阅读此文需要掌握的知识点 回调函数 模板类 类模板 栈 示例代码 这里直接上代码 pragma once include
  • Qt中的并发

    QThread是一个低级 low level 类 适合用于显式地构建长期运行的线程 QtConcurrent是一个命名空间 提供了用于编写并发软件的更高层次的类和算法 该命名空间中有一个重要的类 QThreadPool 这是一个管理线程池的
  • [第四届-强网杯]:Funhash

  • C#的基本知识

    1 static修饰符 本页介绍 static 修饰符关键字 static 关键字也是 using static 指令的一部分 使用 static 修饰符可声明属于类型本身而不是属于特定对象的静态成员 static 修饰符可用于声明 sta
  • Spring中事务几个常见的问题

    首先 事务这个概念是数据库层面的 Spring只是基于数据库中的事务进行扩展 以及提供了一些能让程序员更新方便操作事务的方式 Spring如何处理事务 Spring中支持编程式事务和声明式事务管理两种方式 1 编程式事务 可以使用Trans
  • 一天内Boss转发5k次,「高性能Java:核心原理案例实战」已被封杀

    前言 市面上讲Java框架的书很多 包括SpingBoot SpringCloud Kafka等 但这些书通常只会让你技术的 量 增长 而 质 仍处于SSM的阶段 而且互联网上并没有体系化 结构化的提升技术的 质 的教材 于是团长行动了起来
  • ubuntu环境下编译内核详解

    一 下载源代码和编译软件的准备 下载内核源代码 http www kernel org 注意 点击2 6 25内核的F版 即完整版 如果你懒得去网站点联接 运行下列命令 代码 cd wget http www kernel org pub
  • c语言代码中调用系统命令行.sh shell脚本,linux shell system传参

    C语言代码中调用命令行 1 使用system 命令行 执行完命令行后 会返回原先C代码的位置 继续执行 2 如果命令行中需要传参 使用 sprintf 先处理好命令行的内容 再 system system echo 123 int a 3
  • C/C++基本数据类型所占字节数

    关于这个基本的问题 很早以前就很清楚了 C标准中并没有具体给出规定那个基本类型应该是多少字节数 而且这个也与机器 OS 编译器有关 比如同样是在32bits的操作系统系 VC 的编译器下int类型为占4个字节 而tuborC下则是2个字节
  • 文件的结构及存取方法

    文件的组织形式是文件的结构 从不同的角度分析文件有不同的结构形式 逻辑结构和物理结构 从用户角度出发 研究文件的抽象组织方式而定义的文件组织形式为文件的逻辑结构 从系统的角度出发 研究文件的物理组织方式而定义的文件组织形式为文件的物理结构
  • 【虚拟机】VMware16保姆级安装教程

    大家好 我是雷工 工作中需要用到各种各样的工控软件 有时候甚至需要不同版本的软件 但频繁装卸软件比较麻烦 而且像WinCC和博图软件对系统要求比较严格 卸载重装可能就出问题 此时就不得不重装系统 重装系统各种软件都需要重装一遍 费时费力 这
  • 七、Python基础(异常、模块、文件操作)

    七 Python基础 异常 模块 文件操作 目录 七 Python基础 异常 模块 文件操作 一 异常 1 抛出异常 2 简单的捕获异常语法 3 错误类型的捕获 4 异常捕获的完整语法 5 异常的传递 6 raise 主动抛出异常 二 模块
  • 关于面向对象中的get 和set方法的总结,为什么不用public的详解,详解。

    我们都知道去构造一个实体类的时候 标准都是去 private 一个私有变量 然后再给这个私有 变量加上 公开 get 和 set 我总是会忍不住去想一下 为什么不直接去public 变量 是为了什么 是一种标准 还是说有什么好处 发现网上确
  • 行为驱动开发(BDD)你准备好了吗?

    GitChat 作者 冰尘 原文 行为驱动开发 BDD 你准备好了吗 关注微信公众号 GitChat 技术杂谈 一本正经的讲技术 不要错过文末彩蛋 这个Chat笔者将会和大家一起探讨下面的主题 什么是行为驱动开发 BDD 为什么使用行为驱动