一、概念
1.1 BDD简述
BDD(BehaviorDriven Development:行为驱动开发)为用户提供了从开发人员和客户的需求创建测试脚本的机会。它强调将软件开发的过程聚焦于需求和行为。因此,开始时,开发人员,项目经理,QA、 产品都齐聚一堂,集思广益,讨论应该传递哪些测试场景,以便成功调用此软件/应用程序。
实现步骤如下:
-
定义用户故事:使用简短的故事描述,例如“As a [type of user], I want [some goal] so that [some benefit]”。这样可以清晰地了解用户需要和软件的目标。
-
创建场景:通过对故事进行细化和拆分,可以创建多个场景。每个场景都应包括特定的输入和期望输出。
-
撰写特性文件:将用户故事和场景集成到文档中,例如Gherkin语言格式。
-
实现测试代码:编写测试代码以实现每个场景的期望输出。测试代码应该直接映射到故事和场景。
-
运行测试:运行测试以确保软件满足行为和需求。
-
自动化测试:将测试代码集成到自动化测试套件中,以便在以后进行快速回归测试。
-
确认BDD的价值:BDD的价值在于通过聚焦于用户需求和行为,帮助开发团队快速反馈和快速迭代。因此,每个BDD实践都应该评估它是否实现了这些目标。
1.2 例子
比如正在开发一个用户登录功能,这是可能有如下几个关键测试场景,为了能成功调用它这些场景必须测试通过。
-
用户使用正确的用户名、正确的密码登录成功
-
用户使用错误的用户、正确的密码不能成功登录
-
用户使用正确的用户名、错误的密码不能成功登录
-
用户使用错误的用户名、错误的密码不能成功登录
1.3 BDD工作流程
代码必须通过在BDD中定义的测试脚本。如果没有发生,将需要代码重构。只有在成功执行定义的测试脚本后,代码才被冻结。测试流程:
二、实现
2.1 Cucumber框架
BDD的java实现可以使用Cucumber框架。
Cucumber是一个支持行为驱动的开发的开源工具。 更准确地说,Cucumber可以定义为一个测试框架,由简单的英语文本驱动。它作为文档、自动化测试和开发帮助。
它可以在以下步骤中描述:Cucumber读取在要素文件中以纯英语文本编写的代码;它找到步骤定义中完全匹配的每个步骤。
Cucumber优于其他工具的优点
Ø Cucumber支持不同的语言,例如Java、.net、Ruby
Ø 它充当业务与技术间桥梁的角色。可以通过在纯英文文本中创建一个测试用例来实现这一点。
Ø 它允许在不知道任何代码的情况下编写测试脚本,它允许非程序员参与。
Ø 它以端到端测试框架为目的
Ø 由于简单的测试脚本架构,Cucumber提供了代码可重用性
2.2引入maven依赖
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>6.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>6.7.0</version>
<scope>test</scope>
</dependency>
2.3创建.feature文件
.feature文件是一种使用Gherkin语言编写的文件,它描述了软件系统的行为和需求,Gherkin是一种简单的英语文本语言,它有助于工具--Cucumber解释和执行测试脚本。
写入Cucumber测试的文件称为Featurefiles。对于每个被测功能,建议应该有一个单独的feature file。feature file的扩展名必须为“.feature”。可以根据需要创建任意数量的feature file。为了具有有组织的结构,每个feature应当具有一个feature file。例如:
用于feature的名称,featurefile的命名约定取决于个人的选择。在Cucumber中没有关于名字的基本规则。一个简单的feature file由以下关键字/部分组成:
Background :通常具有在每个场景运行之前要设置什么的指令。但是,它在“Before”hook之后执行。因此,当我们想要设置Web浏览器或者我们想要建立数据库连接时,这时最佳的运用代码的方式。示例:
Feature:待测功能的名称。
Description(可选):描述测试中的功能。
Scenario:测试场景。
Given:在执行测试步骤之前的先决条件。
When:为了执行下一步骤,应该匹配的特定条件。
Then:如果满足WHEN中提到的条件,应该会发生什么。
Feature: annotation
#This is how background can be used to eliminate duplicate steps
Background: User navigates to CSDN
Given I am on CSDN login page
#Scenario with AND
Scenario: Failed Login
Description : test fail
When I enter username as "TOM"
And I enter password as "JERRY"
Then Login should fail
#Scenario with BUT
Scenario: Failed Login
Description : test fail
When I enter username as "TOM"
And I enter password as "JERRY"
Then Login should fail
But Relogin option should be available
#Scenario Successful
Scenario: Successful Login
When I enter username as "Tom"
And I enter password as "Tom"
Then Login should successful
2.4创建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();
}
}
2.5 创建一个runner 类文件
@CucumberOptions(features = "src/test/java/Annotation/test.feature")
此路径需要配置 feature的文件路径,否则无法执行
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简介
Scenario Outline基本上用表中的值替换变量/关键字。表中的每一行都被认为是一个场景。继续使用登录功能的例子。到目前为止,一直在执行一个场景:提供正确的用户名,登录成功。
现在,假设我们要检查所有三种可能的输入类型的登录是否成功,这三种类型的输入是用户名,电子邮件地址或电话号码。为了实现这一点,将需要写三个不同的场景,其中每个场景将随输入类型而变化,登录成功。在这种情况下,不使用Scenario Outline 需要这样写:
Scenario:
Given user navigates to Facebook
When I enter correct username and password
Then login should be successful
Scenario:
Given user navigates to Facebook
When I enter correct email address and password
Then login should be successful
Scenario:
Given user navigates to Facebook
When I enter correct phone number and password
Then login should be successful
语句是相同的,只有输入参数(用户名/电子邮件地址/电话号码)正在改变。
所以这里我们需要用,Scenario Outline。当用Scenario Outline定义任何场景时,可以指定一个测试场景,在它的底部,提供一些输入。场景将执行与提供的输入数一样多的次数。 (类似于ddt 测试驱动,可以有多个数据注入)
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的设置。
总结:当场景不更改,但只有数据值更改时,建议使用场景大纲数据表。