BDD行为驱动测试实践

本文介绍了BDD(行为驱动开发)的概念,强调了它如何通过用户故事和场景聚焦于需求和行为。Cucumber作为一个支持BDD的Java测试框架,通过Gherkin语言简化了测试脚本的编写,使得非程序员也能参与。文章还详细阐述了Cucumber的使用,包括创建.feature文件和步骤定义文件,以及如何通过ScenarioOutline进行数据驱动测试。
摘要由CSDN通过智能技术生成

一、概念

1.1 BDD简述


BDD(BehaviorDriven Development:行为驱动开发)为用户提供了从开发人员和客户的需求创建测试脚本的机会。它强调将软件开发的过程聚焦于需求和行为。因此,开始时,开发人员,项目经理,QA、 产品都齐聚一堂,集思广益,讨论应该传递哪些测试场景,以便成功调用此软件/应用程序。

实现步骤如下:

  1. 定义用户故事:使用简短的故事描述,例如“As a [type of user], I want [some goal] so that [some benefit]”。这样可以清晰地了解用户需要和软件的目标。

  2. 创建场景:通过对故事进行细化和拆分,可以创建多个场景。每个场景都应包括特定的输入和期望输出。

  3. 撰写特性文件:将用户故事和场景集成到文档中,例如Gherkin语言格式。

  4. 实现测试代码:编写测试代码以实现每个场景的期望输出。测试代码应该直接映射到故事和场景。

  5. 运行测试:运行测试以确保软件满足行为和需求。

  6. 自动化测试:将测试代码集成到自动化测试套件中,以便在以后进行快速回归测试。

  7. 确认BDD的价值:BDD的价值在于通过聚焦于用户需求和行为,帮助开发团队快速反馈和快速迭代。因此,每个BDD实践都应该评估它是否实现了这些目标。

1.2 例子

比如正在开发一个用户登录功能,这是可能有如下几个关键测试场景,为了能成功调用它这些场景必须测试通过。

  1. 用户使用正确的用户名、正确的密码登录成功

  2. 用户使用错误的用户、正确的密码不能成功登录

  3. 用户使用正确的用户名、错误的密码不能成功登录

  4. 用户使用错误的用户名、错误的密码不能成功登录

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的设置。

总结:当场景不更改,但只有数据值更改时,建议使用场景大纲数据表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值