场景一:业务分析人员觉得自己分析的需求已经写的很清晰了,并且跟技术人员进行了足够的沟通,可是开发完做Desk check的时候,发现所开发的功能还是跟期望有差距。
场景二:开发团队辛辛苦苦开发完一个功能,满怀信心的去给产品经理/客户展示的时候,才发现原来客户需求的功能不是这样的。
这些场景是不是似曾相识?为什么会这样?
一、BDD概念
BDD:将业务需求转化为高质量的软件
在软件开发的广阔世界中,行为驱动开发(Behavior Driven Development,简称BDD)逐渐成为一种重要的开发实践,因为它提倡将业务需求作为开发的驱动力,而不是仅仅关注代码本身。这篇文章将详细介绍BDD的核心概念、方法和价值。
BDD的基本理念
BDD是一种敏捷软件开发的技术,它倡导使用简洁易懂的语言来描述软件的期望行为,这有助于所有团队成员(包括业务人员、开发人员、测试人员等)共享对软件功能的理解。在BDD中,一个重要的工具是“场景”(Scenarios),它描述了给定某种情况下,软件应该如何行为。
关注的是业务领域,而不是技术:BDD强调用领域特定语言(DSL, domain specific language)描述用户行为,定义业务需求,而不会关心系统的技术实现。
不是工具,强调的是一种协作方式:BDD要求各个角色共同参与系统行为的挖掘和定义,以实现对业务价值的一致理解。
不是关于测试的:BDD源自TDD,又不同于TDD,重点不是关于测试的,但可以指导更好的做自动化测试。
全栈敏捷方法:BDD促使团队所有角色从需求到最后的测试验证,进行高度的协作和沟通,以交付最有价值的功能。
BDD 中的常见概念
Epic: 史诗,一般指一个版本或一批功能更新
Feature: 特性,一般指一个功能点,如登录,添加商品,查询商品等,在测试中对应一个测试套件
Scenario:场景,即Story,一个明确的场景,对应一个测试用例
Step: 步骤,测试步骤有Given/When/Then三种
Given: 假设,给定数据或前置条件,对应测试中的setup
When: 当…时,对应一个测试步骤
Then: 然后,即期望结果,对应一个测试断言
And: 同上,可以用于Given/When/Then后
BDD的优势
BDD的优点在于它提供了一种方式,使得所有的团队成员都能对软件的需求有一个共同的理解。通过使用简洁易懂的语言来描述软件的行为,非技术人员也能理解并参与到软件开发过程中。此外,由于测试是在编写代码之前就编写好的,因此可以确保所有的功能都能正常工作,并且随着开发的进行,团队可以持续地对软件进行验证。
BDD的挑战
虽然BDD有很多优点,但是它也有一些挑战,比如需要投入时间来编写和维护测试,以及需要所有的团队成员都理解并采纳BDD的理念等。
尽管如此,BDD仍然是一个非常有价值的开发实践,它使得软件开发更加以用户为中心,有助于提高软件的质量,并且使得团队能够更加有效地进行协作。无论你是一名开发人员,测试人员,还是产品经理,都应该考虑在你的工作中采用BDD。
BDD测试在CI/CD 阶段的位置
这是开发者提交代码更改到代码存储库的阶段,所有新的代码更改都会自动触发构建和测试过程。在这个阶段,BDD测试(以及单元测试和集成测试)通常会运行,以确保新代码的更改或新增功能没有引入新的错误或改变了预期的行为。
在这个阶段,所有的提交都会通过实际生产环境的完全自动化的测试。在这个阶段,BDD测试可以用来验证工作流程和业务场景,保证软件的行为与预期一致。
这个阶段指的是自动将更新的应用版本部署到生产环境的过程。虽然BDD测试通常在持续部署之前就完成了,但是如果你在部署之后有进行任何形式的后期验证(例如: 实时监控,日志分析等),那么这些验证应该再次验证所有预期的行为仍然按预期工作。
二、BDD实施
BDD实施步骤
BDD实施工具
使用通用领域语言编写规格
实现规格的自动化测试
三、用Cucumber实现BDD示例
Cucumber框架
它充当业务与技术间桥梁的角色。可以通过在纯英文文本中创建一个测试用例来实现这一点。
1)引入maven依赖
2)创建.feature文件
3)创建step定义文件
package Annotation;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class Annotation {
WebDriver driver = null;
@Given("I am on CSDN login page")
public void goToCsdn() {
System.setProperty("webdriver.chrome.driver","src/test/resources/chromedriver.exe");
driver=new ChromeDriver();
driver.navigate().to("https://passport.csdn.net/account/login?ref=toolbar");
}
@When("I enter username as \"(.*)\"$")
public void enterUsername(String arg1) {
driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/div[2]/div[1]/div/div[1]/span[4]")).click();
driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/div[2]/div[1]/div/div[2]/div/div[1]/div/input")).sendKeys(arg1);
}
@When ("I enter password as \"(.*)\"$")
public void enterPassword(String arg1) {
driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/div[2]/div[1]/div/div[2]/div/div[2]/div/input")).sendKeys(arg1);
driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/div[2]/div[1]/div/div[2]/div/div[4]/button")).click();
}
@Then("Login should fail$")
public void checkFail() {
if(driver.getCurrentUrl().equalsIgnoreCase("http://my.csdn.net/my/mycsdn")){
System.out.println("Test1 Pass");
}
else {
System.out.println("Test1 Failed");
}
driver.close();
}
@Then("Login should successful")
public void checkSuccessful() {
if(driver.findElement(By.xpath("//*[@id=\"WAF_NC_WRAPPER\"]")).isEnabled()){
System.out.println("Test1 Pass");
}
else {
System.out.println("Test1 Failed");
}
}
@Then("Relogin option should be available$")
public void checkRelogin() {
if(driver.getCurrentUrl().equalsIgnoreCase("http://my.csdn.net/my/mycsdn")){
System.out.println("Test2 Pass"); }
else {
System.out.println("Test2 Failed");
}
driver.close();
}
}
4) 创建一个runner 类文件
package Annotation;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(features = "src/test/java/Annotation/test.feature")
public class runTest {
}
2.6. Scenario Outline简介
Feature: Scenario Outline
Scenario Outline: Login functionality for a social networking site.
Given user navigates to CSDN
When I enter Username as "<username>" and Password as "<password>"
Then login should be unsuccessful
Examples:
|username |password |
|username1 |password1 |
|username2 |password2 |
|username3 |password3 |
package Annotation;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class OutLine {
WebDriver driver = null;
@Given("user navigates to CSDN$")
public void goToFacebook() {
System.setProperty("webdriver.chrome.driver","src/test/resources/chromedriver.exe");
driver=new ChromeDriver();
driver.navigate().to("https://passport.csdn.net/account/login?ref=toolbar");
}
@When("I enter Username as \"([^\"]*)\" and Password as \"([^\"]*)\"$")
public void I_enter_Username_as_and_Password_as(String arg1, String arg2) throws InterruptedException {
driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/div[2]/div[1]/div/div[1]/span[4]")).click();
Thread.sleep(2000);
driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/div[2]/div[1]/div/div[2]/div/div[1]/div/input")).sendKeys(arg1);
driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/div[2]/div[1]/div/div[2]/div/div[2]/div/input")).sendKeys(arg2);
driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/div[2]/div[1]/div/div[2]/div/div[4]/button")).click();
}
@Then("login should be unsuccessful$")
public void validateRelogin() {
if(driver.getCurrentUrl().equalsIgnoreCase("http://my.csdn.net/my/mycsdn")){
System.out.println("Test Pass");
}
else {
System.out.println("Test Failed");
}
}
}
注意:在上面的代码中,必须定义一个具有两个输入参数的函数:一个用户名和另一个用于密码。因此,对于Examples标记中提供的每组输入,将执行GIVEN,WHEN和THEN的设置。