Cucumber-秘籍-全-
Cucumber 秘籍(全)
原文:
zh.annas-archive.org/md5/c96140eae61f828625970f3249eb62b9译者:飞龙
前言
Cucumber JVM 是增长最快的工具之一,它提供了一个前沿的平台来概念化和实现行为驱动开发(BDD)。Cucumber 中提供的各种功能增强了业务和开发团队实施 BDD 的体验。
这本烹饪书大约有 40 个食谱。它带你进行一次学习之旅,从基本概念如特性文件、步骤定义开始,然后过渡到高级概念如钩子、标签、配置以及与 Jenkins 和测试自动化框架的集成。每一章都有多个食谱,第一个食谱介绍该章节的主要概念;随着你在章节中的进展,每个食谱的复杂度逐渐增加。本书为产品负责人、业务分析师、测试人员、开发人员以及所有希望实施 BDD 的人提供了足够的话题。
这本书的写作假设读者已经对 Cucumber 有所了解。如果你是 Cucumber 的新手,建议你先阅读我的博客:
-
博客 1:如何将 Eclipse 与 Cucumber 插件集成
shankargarg.wordpress.com/2015/04/26/how-to-integrate-eclipse-with-cucumber-plugin/ -
博客 2:通过整合 Maven-Cucumber-Selenium-Eclipse 创建 Cucumber 项目
shankargarg.wordpress.com/2015/04/29/create-a-cucumber-project-by-integrating-maven-cucumber-selenium-eclipse/
这两个博客将帮助你集成 Cucumber 和 Eclipse,并帮助你创建和运行一个基本项目。
本书中的所有代码都已提交到 GitHub。以下是代码仓库的 URL:github.com/ShankarGarg/CucumberBook.git。
本仓库包含五个项目:
-
Cucumber-book-blog:该项目用于前面提到的博客中,以 Cucumber、Maven 和 Eclipse 作为起点
-
CucumberCookbook:该项目用于第一章到第五章
-
CucumberWebAutomation, CucumberMobileAutomation, 和 CucumberRESTAutomation:该项目用于第六章,构建 Cucumber 框架
本书涵盖内容
第一章,编写特性文件,涵盖了 Cucumber 的独特方面——Gherkin 语言及其用于编写有意义的智能特性文件的使用。本章还将涵盖不同的关键词,如文件场景、步骤、场景概述和数据表。
第二章, 创建步骤定义,涵盖了 Glue Code/步骤定义的基本概念和用法,以及正则表达式来制定高效和优化的步骤定义。本章还将详细阐述字符串和数据表转换的概念。
第三章, 启用固定装置,涵盖了通过标签和钩子实现固定装置的高级概念。在这里,不仅解释了标签和钩子的个别概念,还解释了使用标签和钩子组合的实践示例。
第四章, 配置 Cucumber,涉及 Cucumber 与 JUnit 的集成以及 Cucumber 选项的概念。在这里,你将学习使用 Cucumber 选项的各种实际示例以及 Cucumber 可以生成的不同类型的报告。
第五章, 运行 Cucumber,涵盖了从终端和从 Jenkins 运行 Cucumber 的主题。你将学习 Cucumber 与 Jenkins 和 GitHub 的集成以实现持续集成和持续部署(CICD)管道。然后你将学习并行执行以充分利用 Cucumber。
第六章, 构建 Cucumber 框架,涵盖了创建健壮的测试自动化框架的详细步骤,以自动化 Web 应用程序、移动应用程序和 REST 服务。
你需要这本书什么
在开始使用 Cucumber 之前,让我们确保我们已经安装了所有必要的软件。
Cucumber 的先决条件如下:
-
Java(版本 7 或更高)作为编程语言
-
Eclipse 作为 IDE
-
Maven 作为构建工具
-
Firefox 作为浏览器
-
Eclipse-Maven 插件
-
Eclipse-Cucumber 插件
-
Jenkins
-
GIT
-
Appium
-
Android SDK
这本书面向谁
本书旨在为希望使用 Cucumber 进行行为驱动开发和测试自动化的商业和开发人员编写。对 Cucumber 有一定了解的读者将发现本书最有益。
由于本书的主要目标是创建测试自动化框架,因此之前的自动化经验将有所帮助。
部分
在这本书中,你会发现一些经常出现的标题(准备就绪、如何操作、工作原理、更多内容以及相关内容)。
为了清楚地说明如何完成一个菜谱,我们使用以下部分如下:
准备就绪
这一部分告诉你菜谱中可以期待什么,并描述了如何设置任何软件或任何为菜谱所需的初步设置。
如何操作…
这一部分包含遵循菜谱所需的步骤。
工作原理…
这一部分通常包括对上一部分发生情况的详细解释。
更多内容…
本节包含有关食谱的附加信息,以便使读者对食谱有更深入的了解。
参见
本节提供了有关食谱的其他有用信息的链接。
惯例
在这本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称将如下所示:“我们可以通过使用include指令来包含其他上下文。”
代码块设置如下:
@When("^user enters \"(.*?)\" in username field$")
public void user_enters_in_username_field(String userName) {
//print the value of data passed from Feature file
System.out.println(userName);
}
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
Scenario: checking pre-condition, action and results
Given user is on Application landing page
When user clicks Sign in button
Then user is on login screen
任何命令行输入或输出都应如下编写:
mvn test -Dcucumber.options="--tags @sanity"
新术语和重要词汇将以粗体显示。屏幕上显示的单词,例如在菜单或对话框中,将以如下方式显示:“点击构建的时间戳。然后点击控制台输出。”
注意
警告或重要注意事项将以如下框中显示。
小贴士
技巧和窍门将以如下方式显示。
读者反馈
我们欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢或不喜欢什么。读者反馈对我们很重要,因为它帮助我们开发出您真正能从中获得最大价值的标题。
要向我们发送一般反馈,请简单地发送电子邮件至 <feedback@packtpub.com>,并在邮件主题中提及书籍标题。
如果您在某个主题领域有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在您已经是 Packt 图书的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大价值。
下载示例代码
您可以从您在www.packtpub.com的账户下载示例代码文件,适用于您购买的所有 Packt Publishing 书籍。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
错误更正
尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误,请访问 www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。
盗版
互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现任何形式的我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过 <copyright@packtpub.com> 与我们联系,并提供疑似盗版材料的链接。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果您对本书的任何方面有问题,您可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决问题。
第一章.编写特性文件
在本章中,我们将涵盖以下主题:
-
使用一个场景编写你的第一个特性文件
-
使用不同步骤创建场景
-
使用 And 和 But 关键字创建场景
-
使用多个场景编写特性文件
-
向特性文件添加背景
-
在步骤中发送多个参数
-
使用复杂的数据类型来存储数据
-
实现场景概述
-
使用非英语语言创建特性文件
-
结合场景、背景和场景概述
简介
在 Cucumber 框架中,业务需求在特性文件中指定,这些文件是用 Gherkin 语言编写的。因此,了解 Gherkin 语言的力量和用法,以便创建高效和优化的特性文件对我们来说非常重要。
本章将涵盖使用 Gherkin 语言编写有意义的和智能特性文件的使用方法。我们将从一些简单的菜谱开始,创建一个只有一个场景的特性文件,然后逐渐过渡到更复杂的菜谱,其中创建具有多个场景、背景和场景概述的特性文件。我们还将涵盖概念和关键字,如特性、场景、步骤、背景、场景概述和数据表。
注意
在本章中,我们只关注特性文件。步骤定义和自动化库将在后面的章节中介绍。最初,你可能无法完全理解本章中的所有概念,但随着你的阅读,一切都会变得清晰起来。
使用一个场景编写你的第一个特性文件
假设你是产品所有者(PO)或业务分析师(BA)。你的团队正在创建一个网络应用程序,你需要为该应用程序编写规范。该网络应用程序的一个非常简单和基本的规范是,当我们在一个浏览器中输入该应用程序的 URL 时,该应用程序应该加载。那么我们如何在 Cucumber 中编写这个规范?我们将在这个菜谱中介绍。
如何做到这一点…
在这个菜谱中,我们将创建一个简单的特性文件,其中只有一个场景来测试网页是否已加载。
让我们创建一个page_load.feature文件:
Feature: Test Git web Application
In order to Test Git web Application
As a user
I want to specify the application flow
Scenario: Web Site loads
application page load should be quick
Given application URL is ready with the user
When user enters the URL in browser
Then application page loads
它是如何工作的…
在 Cucumber 中,我们用纯英文编写我们的需求,就像语言、Gherkin。Gherkin 是一种具有非常明确语法的特定领域语言。它基于一些预定义的关键字。在先前的例子中,文本中突出显示的部分是 Gherkin 的关键字,其余部分则依赖于正在测试的应用程序。
让我们更详细地理解每个关键字。
特性
在 Cucumber 中,特性文件包含业务需求。特性关键词之后立即跟随的文本,并且位于同一行,是特性文件的标题。特性关键词行之后跟随的三个(可选)文本行是特性文件的意图。意图文本是我们想写的任何内容,直到第一个场景。特性文件应包含场景或场景轮廓。特性文件的命名约定应该是小写并带有下划线,例如,login.feature 和 home_page.feature。场景和特性文件的名称必须是唯一的。
场景
场景类似于测试用例,并以新行中的场景关键词开始(不同于特性意图)。场景关键词之后立即跟随的文本,并且位于同一行,是场景标题。在场景关键词行之后跟随的三个(可选)文本行是场景的意图。所有场景都执行以下操作:
-
将系统置于特定状态
-
摇晃它(执行某些操作)
-
检查新状态
步骤
场景包含步骤,这些步骤相当于测试步骤,并使用以下关键词来表示:Given、When、Then、But 和 And(区分大小写)。
注意
当你保存本章中提到的特性文件并运行它们时,在第一次运行中,Cucumber 将会因缺少步骤定义文件而给出错误,并附带步骤定义的建议。为了解决这些错误,复制 Cucumber 提供的建议,并将它们粘贴到默认的步骤定义文件中。
使用不同步骤创建场景
当我们指定业务需求时,我们需要指定先决条件、用户操作和预期输出。让我们首先了解这些分别代表什么:
-
先决条件: 这将应用程序(AUT)置于测试用例可以执行的状态,或建立应用程序上下文。
-
用户操作: 这指的是用户执行的动作,该动作与场景目标一致。
-
预期输出: 这指的是用户操作后应用程序的响应。
因此,让我们在这个菜谱中将这个规范写成 Cucumber 格式。
如何操作…
在这个菜谱中,我们将通过使用关键词 Given、When 和 Then 更新我们在前一个菜谱中创建的特性文件。
Feature: login Page
In order to test login page
As a Registered user
I want to specify the login conditions
Scenario: checking pre-condition, action and results
Given user is on Application landing page
When user clicks Sign in button
Then user is on login screen
它是如何工作的…
Cucumber 场景由以下关键词(如 Given, When, Then, And, But 等)标识的步骤组成。这些步骤的定义如下:
-
Given: 先决条件在
Given关键词中提及。Given关键词的步骤将系统置于已知状态,这对于用户操作是必要的。避免在Given步骤中谈论用户交互。 -
何时:
When步骤的目的在于描述用户操作。 -
Then:
Then步骤的目的是观察预期输出。观察结果应与特性描述的业务价值/好处相关。
使用 And 和 But 关键词创建场景
当我们指定业务需求时,有时会有多个先决条件、用户操作和预期结果。那么我们如何在 Cucumber 中编写这些规范?
准备工作…
根据我们迄今为止所学的内容,我们知道如何使用一个 Given、When 和 Then 关键字来创建场景。现在,如果我们需要添加多个步骤,那么我们可以像这样更新我们的特性文件:
Feature: login Page
In order to test login page
As a Registered user
I want to specify the login conditions
Scenario: without and & but
Given user is on Application landing page
Given Sign in button is present on screen
When user clicks on Sign in button
Then user can see login screen
When user enters "ShankarGarg" in username field
When user enters "123456" in password field
When user clicks Sign in button
Then user is on home page
Then title of home page is "GitHub"
这里的问题是关键字Given、When和Then被重复使用,因此可读性受到影响。保持特性文件的可读性是 Cucumber 最大的优势之一。那么我们如何保持特性文件的可读性?让我们在这个菜谱中找出答案。
如何做到这一点…
在这个菜谱中,我们将添加一个额外的场景,并使用And和But关键字:
Feature: login Page
In order to test login page
As a Registered user
I want to specify the login conditions
Scenario: with and & but
Given user is on Application landing page
And Sign in button is present on screen
When user clicks on Sign in button
Then user is displayed login screen
When user enters "ShankarGarg" in username field
And user enters "123456" in password field
And user clicks Sign in button
Then user is on home page
And title of home page is "GitHub"
But Sign in button is not present
它是如何工作的…
And和But关键字在这里很有用。这些关键字有助于提高特性文件的表达性和可读性:
-
并且:这用于对先前步骤的补充性陈述,表示肯定陈述。
-
但是:这用于对先前步骤的补充性陈述,表示否定陈述。
注意
在步骤定义文件中,And和But被列为 Given/When/Then,即它们出现的关键字之后。步骤定义中没有And和But关键字。
编写包含多个场景的特性文件
特性文件包含特定功能可能的场景。这就像编写所有可能的需求,当特性实现时,它应该满足这些需求。所以让我们在以下部分用 Cucumber 编写这些规范。
如何做到这一点...
我们将创建一个新的特性文件,名为home_page.feature,它将涵盖github.com/的默认内容、训练营部分和顶部横幅内容的功能。我们将为每个功能创建不同的场景。查看以下截图以获得更多清晰度:

Feature: Home Page
In order to test Home Page of application
As a Registered user
I want to specify the features of home page
Scenario: Home Page Default content
Given user is on Github home page
Then user gets a GitHub bootcamp section
And username is also displayed on right corner
Scenario: GitHub Bootcamp Section
Given user is on GitHub home page
When user focuses on GitHub Bootcamp Section
Then user gets an option to setup git
And user gets an option to create repository
And user gets an option to Fork Repository
And user gets an option to work together
Scenario: Top Banner content
Given user is on GitHub home page
When user focuses on Top Banner
Then user gets an option of home page
And user gets an option to search
And user gets settings options
And user gets an option to logout
它是如何工作的…
一个黄瓜特性文件可以根据需要包含任意数量的场景。以下是一些需要注意的点:
-
一个特性文件通常关注应用程序的一个功能,例如登录页面、主页等。
-
一个场景指的是该功能的一个子特性,例如新客户页面、删除客户页面等。
当我们在特性文件中有多个场景时,我们应该始终遵循无状态场景指南。让我们更好地理解这个指南——每个场景都必须有意义,并且应该独立于任何其他场景执行。一个场景/特性的结果不应影响其他场景。
这些是独立场景的好处:
-
特性文件更容易理解且有趣
-
你只能运行场景的一个子集,因为场景本身提到了所有必需的步骤
-
与依赖场景相比,独立场景将更有资格成为并行执行的候选人
小贴士
下载示例代码
您可以从您在
www.packtpub.com的账户下载所有已购买 Packt 出版物的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
将背景添加到特性文件中
当我们编写特性文件时,我们会编写多个场景。现在所有这些场景都从一个特定的点开始。如果我正在编写主页场景,那么我需要从登录功能开始流程。因此,最好将重复的步骤放在一个地方,而不是在所有场景中。让我们在下一节中了解如何做到这一点。
准备工作
根据我们迄今为止所学的内容,我们的特性文件将看起来像这样:
Feature: Home Page
In order to test Home Page of application
As a Registered user
I want to specify the features of home page
Scenario: Home Page Default content
Given a registered user exists
Given user is on GitHub login page
When user enters username
And user enters password
And user clicks on login button
Then user is on Application home page
And user gets a GitHub bootcamp section
Scenario: GitHub Bootcamp Section
Given user is on GitHub loginpage
When user enters username
And user enters password
And user clicks on login button
Then user is on Application home page
When user focuses on GitHub Bootcamp Section
Then user gets an option to setup git
Scenario: Top Banner content
Given user is on GitHub login page
When user enters username
And user enters password
And user clicks on login button
Then user is on Application home page
When user focuses on Top Banner
Then user gets a logout option
这里的问题是前五个语句在所有场景中都是重复的。这影响了特性文件的可读性,并且有很多重复的工作。
这种编写特性文件的方式存在以下问题:
-
重复:许多语句在所有场景中都是重复的
-
可读性:特性文件的可读性受到影响。
-
重复:将这些步骤复制到所有场景中是多余的。
-
可维护性:即使单个步骤发生变化,我们也必须更改所有出现的地方。
如何做到这一点…
我们将更新home_page.feature文件,并使用Background关键字将所有场景中的公共步骤放在一个地方:
Feature: Home Page
In order to test Home Page of application
As a Registered user
I want to specify the features of home page
Background: flow till home page
Given user is on Application home page
When user enters username
And user enters password
And user clicks on login button
Then user is on Application home page
Scenario: Home Page Default content
Then user gets a GitHub bootcamp section
Scenario: GitHub Bootcamp Section
When user focuses on GitHub Bootcamp Section
Then user gets an option to setup git
Scenario: Top Banner content
When user focuses on Top Banner
Then user gets an option of home page
它是如何工作的…
在这里,我们使用了背景关键字。在特性文件中的每个场景或场景轮廓之前,都会执行背景关键字中提到的所有步骤。让我们更详细地了解这个关键字:
-
在一个特性文件中只能有一个背景,它允许我们为特性文件中的所有场景设置一个前提条件。
-
背景就像一个场景,包含多个步骤。
-
背景在每次场景之前运行,但在
BeforeScenario钩子之后。(我们将在第三章中阅读关于钩子的内容,启用固定装置)。 -
背景的标题和多行描述/意图是可选的。
-
由于背景中提到的步骤将在特性文件中的所有场景中运行,因此我们在将步骤添加到背景时需要小心。例如,我们不应该添加一个不是所有场景都共有的步骤。
这就是前面文件输出的样子:

不要使用Background来设置复杂的状态,除非这个状态实际上是客户端需要知道的东西。
-
请保持您的“背景”部分简短,因为当您添加新场景时,您希望人们能够记住这些步骤
-
让你的
背景部分生动有趣,因为这样人们更容易记住它
在步骤中发送多个参数
当我们谈论测试时,数据驱动测试是一个非常著名的方法。到目前为止,我们一直专注于我们的步骤想要做什么。现在浮现出来的问题是以下这些:
-
我们的步骤也可以发送测试数据吗?
-
我们的步骤可以发送什么样的测试数据?
-
我们能否在单个步骤中发送混合数据类型?
穿上业务分析师的鞋子,让我们为 GitHub 用户注册页面和登录功能编写一些场景。
如何做到这一点...
我们将更新login.feature文件,并添加两个场景,我们将通过步骤传递参数:
Feature: login Page
In order to test login page
As a Registered user
I want to specify the login conditions
Scenario: New User Registration
Given user is on Application landing page
When user enters "ShankarGarg" in username field
And user enters "sgarg@gmail.com" in password field
And user enters "123456" in password field
And user clicks on Signup for GitHub button
Then user is successfully registered
Scenario: login
Given user is on Application landing page
And Sign in button is present on screen
When user clicks on Sign in button
Then user is displayed login screen
When user enters "ShankarGarg" in username field
And user enters "123456" in password field
And user clicks Sign in button
Then user is on home page
And title of home page is "GitHub"
它是如何工作的...
在前面的特性文件中,关注用双引号"括起来的文本。这是我们测试数据。步骤中提到的双引号"之间的文本与步骤定义文件中的捕获组相关联。
一个步骤定义的示例是:
@When("^user enters \"(.*?)\" in username field$")
public void user_enters_in_username_field(String userName) {
//print the value of data passed from Feature file
System.out.println(userName);
}
前面的System.out.println的输出将是ShankarGarg(我们在特性文件中传递的测试数据)。
注意
现在,既然你已经学会了如何在步骤中传递测试数据,那么尝试以下操作:
-
在同一个步骤中发送字符串和整数数据。
-
在步骤中发送一个列表;例如:“星期一,星期二,星期三”。
使用复杂的数据类型来存储数据
在前面的食谱中,我们学习了如何在步骤中发送数据,这些数据可以被应用程序用于处理。我们到目前为止发送的数据要么是字符串,要么是整数。但如果我们想发送更复杂的数据结构,并且跨越多行呢?
准备工作
让我们为这个功能编写一个场景——我们想要验证是否存在各种用户:
Scenario: Existing user Verification
Given user is on Application landing page
Then we verify user "Shankar" with password "P@ssword123", phone "999" exists
Then we verify user "Ram" with password "P@ssword456", phone " 888" exists
Then we verify user "Sham" with password "P@ssword789", phone "666" exists
这种编写特性文件的方法的问题是特性文件不够表达,有很多重复。
如何做到这一点...
我们将向login.feature文件添加一个额外的场景,并使用数据表通过步骤发送大量测试数据:
Scenario: Existing user Verification
Given user is on Application landing page
Then we verify following user exists
| name | email | phone |
| Shankar | sgarg@email.com | 999 |
| Ram | ram@email.com | 888 |
| Sham | sham@email.org | 666 |
它是如何工作的…
这里我们使用了数据表。将表格作为步骤的参数对于指定较大的数据集来说非常方便。让我们更详细地了解数据表:
-
将表格作为步骤的参数非常方便,可以指定更大的数据集。
-
数据表的第一行总是标题行,我们在这里指定每列的标题。数据表中的所有其他行都是数据行,它们包含应用程序将使用的实际数据。
-
数据表将作为最后一个参数传递给步骤定义。
-
不要混淆数据表与场景概述表。
-
在步骤定义文件中处理数据表也非常容易。这是一个示例步骤定义代码:
@Then("^we verify following user exists$") public void we_verify_following_user_exists(DataTable userDetails){ //Write the code to handle Data Table List<List<String>> data = userdetails.raw(); System.out.println(data.get(1).get(1)); }
在前面的代码示例中,数据表已经被转换成字符串列表,并且之后可以很容易地处理。
注意
数据表转换已在第二章中详细解释,即转换数据表以解析测试数据配方。
实现场景轮廓
在上一个配方中,我们学习了如何在步骤本身中发送测试数据,这些数据可以被应用程序用于处理。到目前为止,数据与一个特定的步骤相关联(由数据表实现);但如果我们想发送与整个场景相关的数据,或者我们想为不同的数据集重复执行场景的所有步骤,这将是数据驱动测试的一个典型例子。这将通过使用场景轮廓来实现。
准备工作
让我们创建一个用于登录功能的场景,我们想要测试所有可能导致登录失败的场景。根据我们迄今为止所学的内容,我们的场景将看起来像这样:
Scenario: login fail - wrong username
Given user is on Application landing page
When user clicks on Sign in button
Then user is displayed login screen
When user enters "wrongusername" in username field
And user enters "123456" in password field
And user clicks Sign in button
Then user gets login failed error message
Scenario: login fail - wrong password
Given user is on Application landing page
When user clicks on Sign in button
Then user is displayed login screen
When user enters "ShankarGarg" in username field
And user enters "wrongpassword" in password field
And user clicks Sign in button
Then user gets login failed error message
从语法角度来看,这段代码没有问题。Cucumber 会像对待任何其他代码一样处理它,但问题是对于编写特性文件的人来说。如果你仔细看,只有数据集在变化,所有其他步骤都是相同的。这是创建特性文件方法中存在以下问题的原因:
-
复制和粘贴场景以使用不同的值可能会很快变得繁琐和重复。
-
如果明天只有一个步骤发生变化,它必须在所有场景中更改。因此,可维护性和可重用性是重要的问题。
为了避免这些问题,让我们看看下一节,了解我们如何解决这些问题。
如何做到这一点...
在这里,我们将使用Scenario Outline关键字并添加一个场景轮廓来测试可能的登录场景:
Scenario Outline: Login fail - possible combinations
Given user is on Application landing page
When user clicks on Sign in button
Then user is displayed login screen
When user enters "<UserName>" in username field
And user enters "<Password>" in password field
And user clicks Sign in button
Then user gets login failed error message
Examples:
| UserName | Password |
| wrongusername | 123456 |
| ShankarGarg | wrongpassword |
| wrongusername | wrongpassword |
它是如何工作的...
在这里,我们使用了Scenario Outline关键字,并将所有三个场景合并为一个场景轮廓。场景轮廓的一个优点是,我们的特性文件现在更加紧凑和表达性强。让我们更详细地了解场景轮廓:
-
场景轮廓允许我们通过使用带有占位符的模板来向场景发送测试数据。
-
对于场景轮廓下面的示例部分中的每一行(不包括列标题的第一行),都会运行一次场景轮廓。
-
场景轮廓是一个模板,它永远不会直接运行。它使用占位符,这些占位符位于场景轮廓步骤中的
< >内。 -
将占位符想象成一个变量。它会被
Examples表行中的实际值所替换,其中占位符尖括号内的文本与表列标题相匹配。-
在第一次执行中,当 Cucumber 遇到第一个带有占位符的步骤时,在我们的例子中是
当用户在用户名字段中输入<UserName>,Cucumber 会在Examples表中寻找标题为UserName的列。 -
如果在
Examples表中没有带有UserName列,那么 Cucumber 不会报错,而是将<UserName>视为一个字符串,并原样将其传递给步骤定义。 -
如果 Cucumber 找到一个带有
UserName标题的列,那么它会从这个列中选取第一行数据,并用这个值替换UserName,在我们的例子中是wrongusername,然后将这个值发送到步骤定义。 -
Cucumber 会为执行中的一轮中的所有
< >重复此过程。 -
因此,对于第一次执行,我们的情景概述将看起来是这样的:
Given user is on Application landing page When user clicks on Sign in button Then user is displayed login screen When user enters "wrongusername" in username field And user enters "123456" in password field And user clicks Sign in button Then user gets login failed error message
-
-
用于占位符的值在每个情景概述的后续运行中都会改变。第二行的值用于第二次执行,依此类推,直到达到
Examples表的末尾。 -
情景概述本身如果没有
Examples表就毫无用处,该表列出了要替换每个占位符的值行。注意
现在你已经了解了情景概述的概念,尝试实现以下内容:
-
在一个步骤中写一个带有多个参数的情景概述。
-
你能创建一个带有多个示例的情景概述吗?你可以将正测试和负测试的示例分组在不同的表中。
-
在非英语语言中创建特性文件
我们中的大多数人都在跨地理的团队中工作过,我们都会同意,与我们相比,有些人更习惯于母语而不是英语。我们能够更好地表达自己,也能够表达一切。那么,如果我们的业务分析师或产品所有者比英语更习惯于丹麦语呢?让我们在 Cucumber 中以非英语语言编写规范。
如何做到这一点...
这是一个示例英文特性文件,我们将将其转换为不同的语言。
Feature: sample application
In order to test login page
As a Registered user
I want to specify the login conditions
Scenario: sample scenario
Given user is on application page
When user clicks login button
Then user is on home page
要创建丹麦语(Danish.feature)的特性文件:
# language: da
Egenskab: prøve ansøgning
For at teste login side
Som registreret bruger
Jeg ønsker at angive login betingelser
Scenarie: prøve scenario
Givet brugeren er på ansøgning side
Når brugeren klikker login knap
Så Derefter bruger er på hjemmesiden
它是如何工作的...
Cucumber 允许我们用大约 40 种口语化的语言编写特性文件,从而赋予那些母语不是英语的团队编写与英语特性文件一样健壮的特性文件的能力。特性文件第一行的# language: da标题告诉 Cucumber 特性文件中将使用哪种语言。默认情况下,语言是英语。如果我们想用其他语言编写特性文件,特性文件必须以"UTF-8"编码保存。
在一个项目中,我们可以有多个语言的特性文件;但对于一个特性文件,只能使用一种语言。
注意
对于所有语言,步骤定义的生成没有区别。
结合情景、背景和情景概述
到目前为止,我们已经单独学习了情景、步骤、背景和情景概述。但是当一个业务分析师或产品所有者必须编写特性文件时,他们必须结合所有这些关键字来编写一个非常高效和表达性的特性文件。
考虑编写一个登录功能的特性文件,其中后者满足以下标准:
-
用户应有一个选项从应用程序的主页登录
-
要登录,用户应有一个用户名和密码
-
登录成功后,用户应被重定向到主页
-
如果登录失败,用户应收到适当的消息
-
用户还应该有一个选项在主页上注册新用户
-
用户还应该能够验证应用程序中存在哪些用户(这个功能不在 GitHub 着陆页上,但已添加以阐明概念)
注意
所有这些要求都是针对应用程序的行为的,并且它们都不涉及您如何实现这些要求。
如何做到这一点…
现在我们将使用我们迄今为止探索的所有关键词,并创建一个login.feature文件,该文件指定了上述所有要求:
Feature: login Page
In order to test login page
As a Registered user
I want to specify the login conditions
Scenario: login flow
Given user is on Application landing page
And Sign in button is present on screen
When user clicks on Sign in button
Then user is displayed login screen
When user enters "ShankarGarg" in username field
And user enters "123456" in password field
And user clicks Sign in button
Then user is on home page
And title of home page is "GitHub"
Scenario Outline: Login fail - possible combinations
Given user is on Application landing page
When user clicks on Sign in button
Then user is displayed login screen
When user enters "<UserName>" in username field
And user enters "<Password>" in password field
And user clicks Sign in button
Then user gets login failed error message
Examples:
| UserName | Password |
| wrongusername | 123456 |
| ShankarGarg | wrongpassword |
| wrongusername | wrongpassword |
Scenario: Existing user Verification
Given user is on Application landing page
Then we verify following user exists
| Name | Email | Phone |
| Shankar | sgarg@email.com | 999 |
| Ram | ram@email.com | 888 |
| Sham | sham@email.org | 666 |
Scenario: New User Registration
Given user is on Application landing page
When user enters "ShankarGarg" in username field
And user enters "sgarg@gmail.com" in password field
And user enters "123456" in password field
And user clicks on Signup for GitHub button
Then user is successfully registered
它是如何工作的…
在这里,我们已将本章中讨论的所有关键词和概念合并在一起。让我们逐个分析每个要求,并分析我们是如何以及使用哪个关键词来指定这些要求的:
-
用户应有一个选项从应用程序主页登录—场景
-
对于登录,用户应有一个用户名和密码—场景
-
登录成功后,用户应被重定向到主页—场景
-
如果登录失败,用户应收到适当的消息—场景概述
-
用户还应该有一个选项在主页上注册新用户—场景
-
用户还应该能够验证应用程序中存在哪些用户—数据表
第二章:创建步骤定义
在本章中,我们将涵盖以下主题:
-
创建你的第一个步骤定义文件
-
识别重复和模糊的步骤定义
-
使用正则表达式优化步骤定义
-
使用可选捕获/非捕获组
-
将数据表转换为解析测试数据
-
实现数据表差异以比较表格
-
使用文档字符串将大数据解析为一个块
-
结合文档字符串和场景概述
-
定义字符串转换以实现更好的转换
简介
有时候,那些不太熟悉 Cucumber 的人可能会争论说,与没有 Cucumber 的框架相比,创建步骤定义文件是一个额外的负担。但他们没有意识到的是,Cucumber 会自动生成这些步骤定义,所以它并不是一个负担。通过了解本章中涵盖的概念,你将能够编写非常有效和高效的步骤定义。
在本章中,我们将通过涵盖不同类型的步骤定义、正则表达式的使用等详细地介绍粘合代码/步骤定义的基本概念。为了提出优化和高效的步骤定义,我们还将详细阐述文档字符串、数据表转换和捕获组的先进概念。
创建你的第一个步骤定义文件
现在假设你是一名自动化开发者,你必须为特性文件实现自动化测试用例。在这个方向上的下一步将是编写这个特性文件的步骤定义。那么,我们如何在 Cucumber 项目中编写步骤定义呢?让我们看看这个配方中是如何做到这一点的。
如何做到这一点…
创建步骤定义最简单的方法是让 Cucumber 来处理。步骤如下:
-
这是我们在上一章中使用的特性文件。让我们使用这个特性文件来创建我们的第一个步骤定义:
Feature: login Page In order to test login page As a Registered user I want to specify the login conditions Scenario: checking pre-condition, action and results Given user is on Application landing page When user clicks Sign in button Then user is on login screen -
当你保存特性文件并运行(无论是通过 Eclipse 还是通过终端)时,Cucumber 将为缺失的步骤定义文件提供错误以及步骤定义的建议。以下是在 Eclipse 中显示的错误示例
![如何做到这一点…]()
-
注意 Cucumber 给出的建议(在前面的截图中被突出显示)。根据 Cucumber,所有步骤目前都处于未定义状态。
-
复制 Cucumber 给出的建议,并将其粘贴到默认的步骤定义文件中。
注意
记得将步骤定义文件的路径提供给 Cucumber 选项类。
-
这就是我们的
LoginSteps.java类将看起来:package com.StepDefinitions; import cucumber.api.PendingException; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; public class LoginSteps { @Given("^user is on Application landing page$") public void user_is_on_Application_landing_page() throws Throwable { /* Write code here that turns the phrase above into concrete actions*/ throw new PendingException(); } @When("^user clicks Sign in button$") public void user_clicks_Sign_in_button() throws Throwable { /*Write code here that turns the phrase above into concrete actions */ throw new PendingException(); } @Then("^user is on login screen$") public void user_is_on_login_screen() throws Throwable { /* Write code here that turns the phrase above into concrete actions */ throw new PendingException(); } } -
现在,再次运行特性文件并查看 Cucumber 输出:
![如何做到这一点…]()
-
现在我们的场景状态已从未定义变为挂起。参看前一个截图中被突出显示的区域。Cucumber 建议我们为挂起的步骤添加实现。
-
我们将删除类中的 throw 语句,并用一些虚拟代码替换它们。您可以调用实际的自动化代码(无论是网站上的 Selenium 函数还是面向服务的架构(SOA)自动化中的 HTTPClient 函数)。以下是我们的
LoginSteps.java类看起来像:package com.StepDefinitions; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; public class LoginSteps { @Given("^user is on Application landing page$") public void user_is_on_Application_landing_page() throws Throwable { //sample code goes here System.out.println("Given"); } @When("^user clicks Sign in button$") public void user_clicks_Sign_in_button() throws Throwable { //sample Code goes here System.out.println("When"); } @Then("^user is on login screen$") public void user_is_on_login_screen() throws Throwable { //sample Code goes here System.out.println("Then"); } } -
现在,再次执行特性文件并观察 Cucumber 的输出:
![如何做…]()
-
现在,Cucumber 的输出已经改变,我们步骤的状态已从挂起变为通过;此外,每个步骤中调用的每个函数的输出也已打印出来。
注意
不论是使用
@Given、@When还是@Then注解创建的步骤定义,只要正则表达式与步骤的主要文本匹配,步骤定义就会匹配任何 Gherkin 步骤。例如,Given I have 100 in my account和When I have 100 in my account将会匹配到@Given("^I have (.*?) in my account$")。
它是如何工作的…
现在,让我们更详细地了解上一节中执行的每个步骤,以更好地理解步骤定义的概念:
步骤定义
-
当 Cucumber 开始执行时,它会查找类路径上所有位于指定粘合包(或其子包)中的类。
-
步骤定义是一小段代码,其中附加了一个模式。该模式用于将步骤定义链接到所有匹配的步骤,而代码是 Cucumber 在看到 Gherkin 步骤时将执行的内容。
-
我们使用 Cucumber 注解,如
@Given、@When和@Then来创建步骤定义。 -
在步骤
Given user is on Application landing page中,Given关键字之后的文本(user is on Application landing page)是 Cucumber 在步骤定义文件中匹配的内容(@Given("^user is on Application landing page$"))。当 Cucumber 找到匹配项时,它将执行该步骤中提到的函数。
未定义步骤
-
当我们首次执行特性文件时,Cucumber 没有找到任何匹配的步骤定义,这就是为什么 Cucumber 给出了未定义步骤错误。
-
Cucumber 还会给出自己的建议来消除未定义步骤。在场景中未定义步骤之后的后续步骤都将被跳过,场景将被标记为失败。
挂起步骤
-
当我们在步骤定义文件中使用 Cucumber 建议的代码,并运行特性文件时,我们会得到挂起步骤异常。
-
挂起步骤异常是因为以下代码:
throw new PendingException(); -
当 Cucumber 遇到这个语句时,它理解这些步骤仍然处于进行中。
-
场景的状态是挂起,第一个有
PendingException()的步骤将被标记为挂起,该场景中的其他所有步骤都将被跳过。
实现步骤
-
当我们用功能代码替换 throw 命令时,挂起异常错误就会消失。
-
现在,步骤的状态将取决于该步骤中执行的内容。
识别重复和模糊的步骤定义
有时候当我们编写 Cucumber 步骤定义文件时,我们会遇到重复步骤定义错误或模糊步骤定义错误。让我们尝试理解这些错误产生的原因,以及我们如何通过本食谱来消除它们。
如何操作...
我们将使用之前食谱中相同的特征文件。执行以下步骤:
-
让我们在
StepDefinitions包中创建另一个名为DuplicateAmbiguous.java的类,内容如下:package com.StepDefinitions; import cucumber.api.java.en.Given; public class DuplicateAmbiguous { //Duplicate Steps @Given("^user is on Application landing page$") public void user_is_on_Application_landing_page_duplicate() throws Throwable { //sample code goes here System.out.println("Duplicate"); } }当你尝试运行特征文件时,观察 Cucumber 输出:
Exception in thread "main" cucumber.runtime.DuplicateStepDefinitionException: Duplicate Step Definitions in com.StepDefinitions.DuplicateAmbiguous.user_is_on_Application_landing_page_duplicate() in file:/C:/Users/user/Documents/Xebia/Docs/cucumber/Book/Project/target/test-classes/ and com.StepDefinitions.LoginSteps.user_is_on_Application_landing_page() in file:/C:/Users/user/Documents/Xebia/Docs/cucumber/Book/Project/target/test-classes/ at cucumber.runtime.RuntimeGlue.addStepDefinition(RuntimeGlue.java:33) at cucumber.runtime.java.JavaBackend.addStepDefinition(JavaBackend.java:153) at cucumber.runtime.java.MethodScanner.scan(MethodScanner.java:68) at cucumber.runtime.java.MethodScanner.scan(MethodScanner.java:41) at cucumber.runtime.java.JavaBackend.loadGlue(JavaBackend.java:89) at cucumber.runtime.Runtime.<init>(Runtime.java:90) at cucumber.runtime.Runtime.<init>(Runtime.java:68) at cucumber.runtime.Runtime.<init>(Runtime.java:64) at cucumber.api.cli.Main.run(Main.java:35) at cucumber.api.cli.Main.main(Main.java:18) -
Cucumber 显示一个重复步骤定义错误,指出有两个步骤定义是完全匹配的。
-
让我们将
DuplicateAmbiguous.java的内容更改为以下代码:package com.StepDefinitions; import cucumber.api.java.en.Given; public class DuplicateAmbiguous { //Ambiguous Steps @Given("^user is on (.*?) page$") public void user_is_on_Application_landing_page_ambiguous() throws Throwable { //sample code goes here System.out.println("Duplicate"); } } -
现在运行特征文件并观察 Cucumber 输出:
Scenario: checking pre-condition, action and results # C:/Users/user/Documents/Xebia/Docs/cucumber/Book/Project/src/test/java/com/features/login.feature:6 Given user is on Application landing page # DuplicateAmbiguous.user_is_on_Application_landing_page_ambiguous() cucumber.runtime.AmbiguousStepDefinitionsException: ✽.Given user is on Application landing page(C:/Users/user/Documents/Xebia/Docs/cucumber/Book/Project/src/test/java/com/features/login.feature:7) matches more than one Step Definition: ^user is on (.*?) page$ in DuplicateAmbiguous.user_is_on_Application_landing_page_ambiguous() ^user is on Application landing page$ in LoginSteps.user_is_on_Application_landing_page() at cucumber.runtime.RuntimeGlue.stepDefinitionMatch(RuntimeGlue.java:71) at cucumber.runtime.Runtime.runStep(Runtime.java:265) at cucumber.runtime.model.StepContainer.runStep(StepContainer.java:44) at cucumber.runtime.model.StepContainer.runSteps(StepContainer.java:39) at cucumber.runtime.model.CucumberScenario.run(CucumberScenario.java:48) at cucumber.runtime.model.CucumberFeature.run(CucumberFeature.java:163) at cucumber.runtime.Runtime.run(Runtime.java:120) at cucumber.api.cli.Main.run(Main.java:36) at cucumber.api.cli.Main.main(Main.java:18) When user clicks Sign in button # LoginSteps.user_clicks_Sign_in_button() Then user is on login screen # LoginSteps.user_is_on_login_screen() 1 Scenarios (1 failed) 3 Steps (1 failed, 2 skipped) 0m0.000s -
在这里,Cucumber 抛出模糊步骤错误,无法决定使用哪个步骤定义,因为两个步骤定义是部分匹配的。
它是如何工作的...
由于 Cucumber-JVM 在指定的粘合包(或其子包)中查找所有类,因此我们可能会在步骤定义中存在一些重复(部分/完全)。让我们更详细地了解这一点:
-
重复步骤:当 Cucumber 遇到多个完全相同的步骤定义时,它会抛出重复步骤定义异常。
-
模糊步骤:当 Cucumber 遇到多个部分匹配的步骤定义时,它会抛出模糊步骤定义异常。
如果 Cucumber 遇到重复/模糊的步骤,则此类场景的所有其他步骤都将跳过,并且这些场景将被标记为失败。
Cucumber 甚至指定了导致错误的两个实例(参考本食谱第 4 步中显示的 Cucumber 输出中突出显示的代码)。
在一次执行中,只有错误步骤的前两个出现被识别;如果有更多相同步骤的重复出现,则将在下一次执行中识别。
小贴士
要检查步骤定义中没有错误或异常,请在 Cucumber 选项类中使用dryRun = true和strict=true运行特征文件。这将仅检查步骤定义的有效性,而不会执行其内的代码。我们将在后面的章节中详细阅读这些选项。
使用正则表达式优化步骤定义
到目前为止,我们已创建了与步骤一对一关系的步骤定义。但随着我们编写越来越多的特征文件,这种方式编写步骤定义可能会变得繁琐。因此,我们将编写通用的步骤定义,这些定义将适用于遵循特定模式的所有后续步骤,从而减少所需的步骤定义数量。让我们看看在本食谱中如何实现这一点。
如何操作...
-
假设我们正在编写以下场景的步骤定义。
Scenario: login fail - wrong username Given user is displayed login screen When user enters "wrongusername" in username field And user enters "123456" in password field And user clicks Sign in button -
现在运行特性文件,并将 Cucumber 步骤定义建议复制粘贴到
LoginSteps.java类中。这就是LoginSteps.java的样子:package com.StepDefinitions; import cucumber.api.java.en.Given; import cucumber.api.java.en.When; public class LoginSteps { @Given("^user is displayed login screen$") public void user_is_displayed_login_screen() { } @When("^user enters \"(.*?)\" in username field$") public void user_enters_in_username_field(String username) { System.out.println(username); } @When("^user enters \"(.*?)\" in password field$") public void user_enters_in_password_field(String password) { System.out.println(password); } @When("^user clicks Sign in button$") public void user_clicks_Sign_in_button() { } } -
专注于前面代码示例中提到的粗体步骤定义。我们使用了正则表达式并启用单个步骤定义来匹配多个步骤,这与提到的通配符模式相匹配。
它是如何工作的…
Cucumber 允许我们使用正则表达式来增强步骤定义以匹配多个步骤。让我们了解它是如何工作的:
-
捕获组:
当您将正则表达式的一部分用括号括起来时,它就变成了一个捕获组。在 Cucumber 步骤定义中,每个捕获组中匹配的文本被作为参数传递给代码块。
例如,在第二个步骤定义中,捕获组是
wrongusername,它将被传递给变量username。同样,在第三个步骤定义中,捕获组是password,它将被传递给变量password。对于静态类型语言,Cucumber 会自动将这些字符串转换为适当类型。对于动态类型语言,默认情况下不会进行转换,因为没有类型信息。
-
Cucumber 还允许将整数传递到捕获组中。
例如,考虑以下步骤:
Given I have 58 Dollars in my account对于这个步骤,步骤定义将看起来像这样(请关注高亮代码):
@Given("I have (\\d+) Dollars in my account") public void I_have_dollar_acnt(int dollar) { // Do something with the dollars } -
Cucumber 还允许将列表传递到捕获组中。
例如,考虑以下步骤:
Given I am available on "Tuesday,Friday,Sunday"步骤定义将看起来像这样,请关注高亮代码:
@Given("^I am available on \"(.+)\"$") public void i_am_available_on(List<String> days) { System.out.println(days.size()); }
以下是在 Cucumber 中可用的正则表达式类型:
-
点号意味着匹配任何单个字符。
-
星号,一个重复修饰符,告诉我们可以重复多少次。
-
加号,一个重复修饰符,告诉我们可以至少重复一次。
-
\d 代表数字 GitHub,或 [0-9]。
-
\w 代表单词字符,具体为 [A-Za-z0-9_]。
-
\s 代表空白字符,包括制表符或换行符。
注意
您也可以通过大写来否定简写字符类;例如,
\D指的是除了数字 GitHub 之外的任何字符。
使用可选捕获和非捕获组
到目前为止,我们已经介绍了如何在特性文件中为各种关键字编写步骤定义。现在让我们谈谈如何有效地使用步骤定义来处理多个步骤。
考虑一种情况,我们在一个步骤中测试一个积极的情况,而在另一个步骤中测试一个消极的情况——这两个步骤之间的唯一区别只是“No”这个词,而其余句子是相同的。根据我们迄今为止所学的知识,我们将为这两个步骤编写两个步骤定义。但有没有更好的方法来做这件事?让我们看看在这个配方中我们如何做得更好。
如何做到这一点…
-
对于这个配方,考虑以下场景并关注高亮文本:
Scenario: Optional Capture Groups/Alternation #positive Then I see following dollars in my account #negative Then I do not see following dollars in my account Scenario: Optional Non capture Groups Given I have following dollars in my account Given He has following dollars in my account Given User has following dollars in my account使用以下步骤定义来处理两个场景,并关注突出显示的代码:
@Then("^I( do not see| see) following dollars in my account$") public void I_see_or_do_not_see_following_dollars_in_my_account(String seeOrDoNotSee) { //print the value of capture group System.out.println(seeOrDoNotSee); } @Given("^(?:I have|He has|User has) following dollars in my account$") public void have_following_dollars_in_my_account() { // Non Capture groups are not captured in Step } -
现在运行场景;你会看到以下输出:
![如何做…]()
它是如何工作的…
可选捕获组消除了步骤定义的重复,并且肯定可以改善特性文件的可读性。使用可选组,相同的步骤定义可以用于肯定和否定断言。让我们更详细地讨论这个问题:
-
可选捕获组/交替:
在括号中使用管道创建了一个可选组(
Text1|Text 2)。在这里,也可以将多个选项分组。在这个例子中,这个步骤定义将接受Text1或Text2,相应地,Text1或Text2将被作为捕获值传递。 -
可选非捕获组:
在可选捕获组的开头添加
?:创建可选非捕获组。有?:会将该组视为可选的,但它不会被捕获。因此,你不需要像之前描述的可选捕获组那样传递一个参数。
将数据表转换为解析测试数据
在上一章中,我们介绍了如何使用数据表将大量数据发送到单个步骤。现在让我们了解如何在步骤定义中处理数据表。
如何做…
-
假设我们正在编写以下场景的步骤定义:
Scenario: Existing user Verification Given user is displayed login screen Then we verify following user exists | Name | Email | | Shankar | sgarg@email.com | | Ram | ram@email.com | | Sham | sham@email.org | -
现在运行特性文件,并将 Cucumber 步骤定义建议复制粘贴到
LoginSteps.java类中。这些是LoginSteps.java中的附加步骤:@Then("^we verify following user exists$") public void we_verify_following_user_exists(DataTable arg1) throws Throwable { /* Write code here that turns the phrase above into concrete actions For automatic transformation, change DataTable to one of List<YourType>, List<List<E>>, List<Map<K,V>> or Map<K,V>. E,K,V must be a scalar (String, Integer, Date, enum etc) */ throw new PendingException(); } -
在这里,Cucumber 将表格作为
DataTable传递给步骤参数。关注 Cucumber 给出的将表格转换为列表或列表的列表的建议。 -
现在,用以下代码替换 Cucumber 给出的代码建议:
@Then("^we verify following user exists$") public void we_verify_following_user_exists(DataTable userDetails) { List<List<String>> details = userDetails.raw(); for (int i = 0; i < details.size(); i++) { for (int j = 0; j < details.get(i).size(); j++) { System.out.println(details.get(i).get(j)); } } }在这里,我们使用了 Cucumber DataTable API 的
raw()方法将数据表转换为字符串的列表的列表。之后,我们使用了两个for循环来遍历列表的列表的所有元素。这就是 Cucumber 输出的样子:![如何做…]()
它是如何工作的…
数据表作为最后一个参数传递给步骤定义。我们需要将cucumber.api.DataTable导入 Java 代码中。DataTable API 的raw()方法用于将数据表转换为字符串的列表的列表。
我们还可以将数据表转换为各种类型的列表。让我们看看我们能做什么:
-
用户定义变量的列表:
让我们定义一个名为
User的自定义类,它具有名称和电子邮件作为数据成员。标题行用于在通用列表类型中命名字段:public class User { public String name; public String email; }现在,它的步骤定义可以是这样的:
@Then("^we verify following user exists$") public void we_verify_following_user_exists(List<User> userList) { for (User u : userList) { System.out.println(u.name); } }这些步骤定义将接受一个包含用户对象(这是我们自定义的)的列表,并将打印出用户的名字。
-
映射的列表:
使用在上一步骤中定义的
User类,步骤定义可以是这样的:@Then("^we verify following user exists$") public void we_verify_following_user_exists(List<Map<String, String>> userList) { for (Map<String, String> u : userList) { System.out.println(u.get("Name")); } }数据表被转换为以下地图列表:
{Name=Shankar, Email=sgarg@email.com} {Name=Ram, Email=ram@email.com} {Name=Sham, Email=sham@email.org}小贴士
如果你将数据表转换为地图,通常建议不要在 Gherkin 表中有一个顶部行来命名列。
-
标量列表 列表:
使用在前面步骤中定义的
User类,步骤定义可以表示如下:@Then("^we verify following user exists$") public void we_verify_following_user_exists(List<List<String>> userList) { for (List<String> u : userList) { System.out.println(u); } }数据表被转换为以下列表:
[Name, Email] [Shankar, sgarg@email.com] [Ram, ram@email.com] [Sham, sham@email.org]
实现数据表差异以比较表格
考虑一种情况,你需要验证一个以表格形式存在的应用程序响应,这与我们在特征文件中发送的另一个数据表进行比较。当我们测试 REST 服务时,这种验证非常常见。
根据我们迄今为止所学的内容,我们将特征文件中的数据表分解为列表的列表,然后我们将验证这些列表的每个元素与预期的列表(在分解表后获得)进行验证。让我们了解我们如何以更简单的方式做到这一点。
如何做到这一点…
-
对于这个食谱,考虑以下场景:
Scenario: Table Diff Given user send a get request to "localhost:8080/e" Then user gets following response | username | age | | sham | 25 | | ram | 26 | -
现在保存特征文件。在添加以下代码到建议后,我们的步骤定义将看起来像这样:
@Given("^user send a get request to \"(.*?)\"$") public void user_send_a_get_request_to(String arg1) { // lets assume user sends a get request } @Then("^user gets following response$") public void user_gets_following_response(DataTable expectedUsers) { /* lets create a table for system response (actualUsers) */ List<List<String>> actualUsers = new ArrayList<List<String>>(); actualUsers.add( Arrays.asList("username", "age")); actualUsers.add( Arrays.asList("sham", "25")); actualUsers.add( Arrays.asList("ram", "26")); /* Use the diff method to differentiate two data tables */ expectedUsers.diff(actualUsers); }
如何工作…
我们可以将表参数(实际结果)与我们提供的另一个表(预期结果)进行比较。
为了正常工作,两个表都应该以某种格式存在。两个表都应该以列为导向,两个表的第一行应该表示列名。列名必须对每个列是唯一的,并且它们必须匹配。
如果表不同,将抛出异常,并在执行报告中报告两个表之间的差异。其输出将如下所示:

与预期不同的行将被打印两次——第一个(由一个"-"前缀)是预期的,后面跟着另一个(由一个"+"前缀),这是实际返回的。
使用文档字符串将大数据解析为一个块
现在让我们考虑一种情况,其中我们需要在多行中指定大量文本(不是表格形状)——类似于博客或书籍的几行。那么,我们在步骤定义中如何处理这种情况?这被称为在测试步骤中传递文档字符串。根据我们迄今为止所涵盖的内容,我们可以使用正则表达式;但这仅对第一行有帮助,而其他所有行的文本都将被忽略。在这个食谱中,让我们看看如何处理这种情况。
如何做到这一点…
-
让我们考虑以下用于此练习的特征文件中的步骤:
Given a book named "cucumber cookbook" with following body """ Title – Cucumber cookbook Author of this book is first time writer so please excuse the naïve mistakes. But he will definitely improve with chapters he writes. """在这里,高亮显示的文本是我们将传递给步骤定义的文档字符串。
-
保存并运行特征文件,然后复制并粘贴缺失步骤定义的 Cucumber 建议。在添加打印语句后,我们的代码将看起来像这样:
@Given("^a book named \"(.*?)\" with following body$") public void a_book_named_with_following_body(String arg1, String arg2) { //prints the regular expression System.out.println(arg1); //prints the Doc String System.out.println(arg2); } -
这是前面代码的输出:
![如何做到这一点…]()
它是如何工作的…
在编写特性文件时,文档字符串应位于开头和结尾的三相邻引号 """ 内。
在步骤定义中,你不需要对文档字符串做任何特殊处理,因为三引号内的文本将作为步骤定义的最后一个参数传递。在我们的代码中,我们也使用了一个正则表达式来处理文本 Cucumber Cookbook,但没有对文档字符串进行处理,因为文档字符串已经传递给了步骤的最后一个参数。
小贴士
开头 """ 的缩进不重要,尽管常见的做法是将它缩进两个空格从封装步骤。传递给文档字符串的字符串的每一行都将重新对齐到开头 """ 的缩进。
结合文档字符串和场景概述
现在考虑一个情况,当你传递一个文档字符串时,但你必须将其与场景概述结合起来。在这种情况下,Cucumber 的行为是怎样的?
让我们考虑一个情况,我们正在指定电子邮件内容的规范,但内容基于发送电子邮件的角色。那么我们如何在特性文件中指定这样的要求,以及我们如何为它编写步骤定义?让我们在这个食谱中找出答案。
如何做…
-
对于这个食谱,让我们考虑这个场景:
Scenario Outline: E-mail content verification Given I have a user account with <Role> rights Then I should receive an email with the body: """ Dear user, You have been granted <Role> rights. You are <details>. Please be responsible. -The Admins """ Examples: | Role | details | | Manager | now able to manage your employee accounts | | Admin | able to manage any user account on system | -
现在运行特性文件;复制 Cucumber 提供的步骤定义建议,并将其替换为以下步骤定义:
@Given("^I have a user account with (.*?) rights$") public void i_have_a_user_account_with_Manager_rights(String role) { //prints the role System.out.println(role); } @Then("^I should receive an email with the body:$") public void i_should_receive_an_email_with_the_body(String docString) { /* prints the content of Doc String with Scenario outlineSubstitution */ System.out.println(docString); } -
当你运行场景时,这是第 1 行数据替换的输出:
![如何做…]()
这是第 2 行数据替换的输出:
![如何做…]()
它是如何工作的…
在前面的章节中,你学习了如何将两个基本概念结合起来,以解决复杂问题的解决方案。我们在这里使用的两个概念是文档字符串和场景概述。让我们看看它们是如何工作的:
如果你仔细看,在步骤 1 的场景概述的Then步骤中用 <> 高亮的文本与示例表标题行的文本相同。(文本已在步骤 1 中高亮)。因此,我们正在告诉 Cucumber,这个文本只是一个占位符,在执行过程中,它的值将来自示例表。
如前一个食谱所示,在步骤定义中我们不需要对文档字符串做任何特殊处理——它们将自动作为最后一个参数传递。因此,在步骤 2 中我们不需要做任何特殊处理。
现在在步骤 3 中,你可以在第一次执行的输出中看到:<Role> 和 <details> 的值被替换为示例表数据行 1 的数据,在第二次执行中,值被替换为示例表数据行 2 的数据。
定义字符串转换以实现更好的转换
想象一个场景,你希望在测试步骤中将一些字符串转换为代码中的特定字符串。例如,产品负责人在一个步骤中提到了 "29-12-1986",而你希望 Cucumber 将这段文本识别为日期。此外,在一些国家,这可能采用 DD-MM-YYYY 格式,而在其他国家,它可能采用 MM-DD-YYYY 格式。那么我们如何标准化这种转换呢?让我们看看如何在 Cucumber 中实现这一点。
准备工作
考虑以下测试步骤来解决这个问题:
Given My Birthday is on "29-12-1986"
现在,我们可以使用 @Format 字符串转换器将文本转换为日期。
如何做…
将 java.util.Date 导入到你的步骤定义文件中。这样我们的步骤定义将看起来像这样:
@Given("^My Birthday is on \"(.*?)\"$")
public void my_Birthday_is_on(@Format("dd-MM-yyyy") Date bday) {
//prints the text converted to Java.util.Date
System.out.println(bday);
//prints the class of bday to confirm it's a Date
System.out.println(bday.getClass());
}
这是运行前面的步骤后出现的输出:

它是如何工作的…
Cucumber-JVM 允许将字符串转换为各种标量类型。标量类型是从单个字符串值派生出来的类型。Cucumber-JVM 的一些内置标量类型包括 numbers、enums、java.util.Date、java.util.Calendar 等。只要字符串值与 java.util.DateFormat 定义的 Short、Medium、Full 或 Long 格式之一匹配,转换为 java.util.Date 和 java.util.Calendar 将会自动完成。
我们示例中的 "29-12-1986" 与这些格式中的任何一个都不匹配,因此我们必须通过使用 @Format 给 Cucumber 一个提示。我们需要指定两件事:首先是格式,其次是数据类型。
如果 Cucumber-JVM 默认没有提供格式化,我们也可以编写自定义格式化程序。此外,我们甚至可以使用字符串转换来处理日期转换以外的其他情况。
第三章。启用固定装置
在本章中,我们将涵盖以下主题:
-
标记
-
AND 和 OR 标记
-
将钩子添加到 Cucumber 代码中
-
标记钩子
-
使用 AND 和 OR 操作符对标记钩子进行操作
简介
在本章中,我们将讨论如何控制运行哪些特性,以及何时运行它们;例如,我们可能只想运行一组特性文件,比如 Sanity,或者我们可能想在每个场景之后截图。这种类型的控制称为固定装置。Cucumber 允许我们通过标记和钩子的概念启用固定装置。通过了解本章中介绍的概念,你将能够编写非常有效和高效的固定装置。
在本章中,我们将从标记和钩子的基本概念开始,然后是 AND 和 OR 操作,接着我们将介绍标记和钩子组合的强大功能。
标记
假设你是一名自动化架构师,你必须运行一组特定的场景或特性文件。这种情况可能经常发生:比如说我们对一个功能进行了更改,现在我们只想运行该功能的特性/场景。
现在,让我们了解如何将一些场景标记为 Sanity,一些标记为 Regression,一些两者都标记。让我们在下一节中看看。
现在,有home_page.feature文件,我们想将其标记为重要。我们还想将一些场景标记为sanity,一些标记为regression,或者一些两者都标记。那么,在 Cucumber 项目中我们该如何做呢?让我们在下一节中看看。
准备工作
让我们使用以下特性文件来完成这个菜谱:
Feature: Home Page
Background: flow till home page
Given user is on Application home page
Scenario: Home Page Default content
Then user gets a GitHub Bootcamp section
Scenario: GitHub Bootcamp Section
When user focuses on GitHub Bootcamp Section
Then user gets an option to setup git
Scenario: Top Banner content
When user focuses on Top Banner
Then user gets an option of home page
如何做…
-
我们只需要在场景之前简单地写上
@sanity或@regression,在特性文件之前写上@important。参考代码中的高亮文本。这就是我们的更新后的home_page.feature文件应该看起来像的:@important Feature: Home Page Background: flow till home page Given user is on Application home page @sanity Scenario: Home Page Default content Then user gets a GitHub Bootcamp section @regression Scenario: GitHub Bootcamp Section When user focuses on GitHub Bootcamp Section Then user gets an option to setup git @sanity @regression Scenario: Top Banner content When user focuses on Top Banner Then user gets an option of home page -
打开命令提示符并转到
project目录。 -
使用以下命令运行标记为
important的特性文件:mvn test -Dcucumber.options="--tags @important"我们将在下一章中详细探讨这个命令——暂时按原样使用它。
我们将得到以下输出:
![如何做…]()
虽然我们的项目中有很多特性文件,但只有标记为重要的特性文件会被运行,这就是为什么所有场景也被运行的原因。
-
现在,在命令提示符中运行以下命令:
mvn test -Dcucumber.options="--tags @sanity"这是输出结果:
![如何做…]()
现在,只有两个场景被执行,并且执行的两个场景都被标记为@sanity。所以很明显,在运行 Cucumber 时提到的任何场景,只有那些场景会被运行。
它是如何工作的…
标记用于组织特性文件和场景。你通过在Scenario关键字之前的行上放置一个以@字符为前缀的单词来标记场景。
注意
一个特性/场景可以拥有多个标记;只需用空格分隔它们,或者将它们放在不同的行上。
继承是指标签被继承。如果一个特性文件有一个标签,那么 Cucumber 将会将该标签分配给该特性文件中的所有场景和所有场景轮廓。
您可以通过在终端运行 Cucumber 测试用例时使用--tags来自定义运行。以下是一些示例:
-
mvn test -Dcucumber.options="--tags @important"将会运行所有场景(因为我们正在运行与特性关联的标签)。 -
mvn test -Dcucumber.options="--tags @sanity"将会运行与@sanity标签关联的场景。注意
在任何标签前方的特殊字符
~告诉 Cucumber 忽略所有与该标签关联的场景。 -
mvn test -Dcucumber.options="--tags ~@important"将会运行没有与@important标签关联的测试用例。
AND 和 OR 操作符的标签
大多数时候,我们会同时修改许多功能;因此,测试人员测试所有这些功能变得至关重要。有时我们必须运行所有标记为@sanity和@regression的场景,有时我们想要运行feature1或feature2的所有场景。那么在 Cucumber 中我们该如何做呢?让我们在本节中看看。
准备工作
这是我们将用于本菜谱的Feature文件:
@important
Feature: Home Page
Background: flow till home page
Given user is on Application home page
@sanity
Scenario: Home Page Default content
Then user gets a GitHub Bootcamp section
@regression
Scenario: GitHub Bootcamp Section
When user focuses on GitHub Bootcamp Section
Then user gets an option to setup git
@sanity @regression
Scenario: Top Banner content
When user focuses on Top Banner
Then user gets an option of home page
如何操作…
-
如果我们想要运行被标记为
sanity和regression的场景,请在命令提示符中运行以下命令:mvn test -Dcucumber.options="--tags @sanity --tags @regression"这就是输出结果:
![如何操作…]()
只运行被标记为同时具有
@sanity和@regression标签的一个场景。 -
如果我们想要运行被标记为
sanity或regression的场景,请在命令提示符中运行以下命令:mvn test -Dcucumber.options="--tags @regression,@sanity"这就是输出结果:
![如何操作…]()
运行被标记为
@sanity或@regression的场景 -
将
home_page.feature文件更新为以下内容:@important Feature: Home Page Background: flow till home page Given user is on Application home page @sanity @wip Scenario: Home Page Default content Then user gets a GitHub Bootcamp section @regression @wip Scenario: GitHub Bootcamp Section When user focuses on GitHub Bootcamp Section Then user gets an option to setup git @sanity @regression Scenario: Top Banner content When user focuses on Top Banner Then user gets an option of home page -
如果我们想要运行被标记为
@sanity AND @wip或@regression AND @wip的场景,请运行以下命令:mvn test -Dcucumber.options="--tags @sanity,@regression --tags @wip"这就是输出结果:
![如何操作…]()
它是如何工作的…
现在我们来理解为什么在前一节中我们使用了 AND 和 OR 操作符来处理标签:
-
AND 操作:当我们想要运行所有提到的标签的场景时。标签必须在单独的
--tags选项中提及;例如,mvn test -Dcucumber.options="--tags @sanity --tags @Regression"。 -
OR 操作:当我们想要运行具有所提及标签之一的场景时。标签必须在单个
–tags选项中提及,但应以逗号分隔;例如,mvn test -Dcucumber.options="--tags @wip,@sanity"。注意
AND 和 OR 操作可以组合起来以实现更大的灵活性,以确定要运行的内容。
将钩子添加到 Cucumber 代码中
在找出如何运行一些选择性的功能之后,下一件大事是在测试场景之前或之后运行一些代码。这些是测试自动化框架的基本和期望的功能。这种功能的例子包括在执行开始前初始化浏览器和在执行完成后关闭浏览器。那么在 Cucumber 中我们该如何做呢?让我们在这个菜谱中看看。
准备工作
对于这个菜谱,我们将更新 home_page.feature 文件,如下所示:
Feature: Home Page
Background: flow till home page
Given user is on Application home page
@sanity @wip
Scenario: Home Page Default content
Then user gets a GitHub Bootcamp section
@regression @wip
Scenario: GitHub Bootcamp Section
When user focuses on GitHub Bootcamp Section
Then user gets an option to setup git
如何操作…
-
在
automation包中创建一个名为Hooks.java的 Java 类,并将以下代码放入其中(注意粗体和突出显示的文本):package com.automation; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import cucumber.api.java.After; import cucumber.api.java.Before; public class Hooks { public static WebDriver driver = null; public static String browser = "firefox"; public static String baseURL = "https://GitHub.com/"; @Before public static void createDriver() { System.out.println("Inside Before"); createDriver(browser); OpenURL(baseURL); } public static void createDriver(final String browserId) { if (browserId.equalsIgnoreCase("firefox")) { driver = new FirefoxDriver(); } } public static void OpenURL(String baseURL) { //Maximize window driver.manage().window().maximize(); // Open URL on window driver.get(baseURL); } @After public void tearDown() { System.out.println("Inside After"); driver.quit(); } } -
现在,从 Eclipse 本身运行
home_page.feature文件并注意输出:![如何操作…]()
它是如何工作的…
-
钩子:Cucumber 允许我们在测试用例执行过程中的特定点运行一段代码。这是通过钩子实现的。这种钩子的实际应用包括在执行前初始化浏览器和在执行后关闭浏览器。钩子代码通常保存在一个名为
Hooks.java的文件中,但这不是强制性的。 -
@Before:在
feature文件的所有场景之前,包括背景场景,都会运行 Before 钩子。如果有多个Before钩子,则它们将按照编写的顺序运行。 -
@After:After 钩子在
feature文件的所有场景之后运行。如果有多个After钩子,则它们将按照编写的顺序运行。以下为执行顺序:
-
Before 钩子
-
背景
-
场景
-
After 钩子
-
更多内容…
虽然钩子是按照编写的顺序运行的,但可以使用 order 参数来定义自定义执行。默认值是 10000,Cucumber 从低到高运行 @Before 钩子。一个 @Before 钩子的 order 为 100 将在 order 为 20 的钩子之前运行。@After 钩子从高到低运行——因此一个 order 为 200 的 @After 钩子将在 order 为 100 的钩子之前运行。
示例:@Before( order=5 ) 和 @After( order =20)
标记钩子
如果我们只想在特定场景之前执行某些代码,而不是在所有场景之前执行呢?考虑一种情况,我们想在浏览器自动化相关的场景中调用 Selenium Webdriver,在 REST 服务自动化相关的场景中使用 REST 客户端代码。在 Cucumber 中我们该如何做呢?让我们在下一节中看看。
准备工作
对于这个菜谱,我们将使用以下方式更新 home_page.feature:
Feature: Home Page
Background: flow till home page
Given user is on Application home page
@web
Scenario: Home Page Default content on Web
Then user gets a GitHub Bootcamp section
@rest
Scenario: GitHub account REST Service
When user sends a GET request
如何操作…
-
使用以下代码更新
Hooks.java类的代码:package com.automation; import org.openqa.selenium.WebDriver; import cucumber.api.java.After; import cucumber.api.java.Before; public class Hooks { public static WebDriver driver = null; public static String browser = "firefox"; public static String baseURL = "https://GitHub.com/"; @Before("@web") public static void createDriver() { System.out.println("Inside Web Hook"); //sample code } @Before("@rest") public static void createrestBuilder() { System.out.println("Inside REST Hook"); //sample code } @After public void tearDown() { System.out.println("Inside After"); //Sample Code } } -
从 Eclipse 运行
home_page.feature文件并将 Cucumber 异常保存在Step Definitions文件中。 -
再次从 Eclipse 运行
home_page.feature文件;这是您将看到的输出:![如何操作…]()
它是如何工作的…
标记钩子是钩子和标签的组合。当只想为特定场景而不是所有场景执行某些操作时,使用标记钩子。我们必须在钩子后面添加括号中的标签,将其转换为标记钩子。
标记钩子的 AND 和 OR 操作
正如我们可以 AND 和 OR 标签一样,我们也可以 AND 和 OR 标签和钩子的组合。考虑一种情况,我们需要为某些功能执行特定的步骤,例如为 feature1 和 feature2,但不为其他功能执行。在 Cucumber 中我们如何做?让我们在这个菜谱中看看。
准备工作
我们将按照以下方式更新此菜谱的 home_page.feature 文件:
@important
Feature: Home Page
Background: flow till home page
Given user is on Application home page
@sanity
Scenario: Home Page Default content
Then user gets a GitHub Bootcamp section
@regression
Scenario: GitHub Bootcamp Section
When user focuses on GitHub Bootcamp Section
Then user gets an option to setup git
@sanity @regression
Scenario: Top Banner content
When user focuses on Top Banner
Then user gets an option of home page
如何操作
-
要在标记为
@sanity或@regression的场景之前运行钩子代码,将以下代码添加到hooks.java文件中:@Before("@sanity,@regression") public void taggedHookMethod1() { System.out.println("tagged hook - sanity OR regression"); } -
从 Eclipse 运行
feature文件并观察输出:![如何操作]()
代码将在所有场景之前运行,因为所有场景要么被标记为
@sanity,要么被标记为@regression。 -
要为同时标记为
@sanity和@regression的场景运行钩子代码,在Hooks.java文件中注释掉之前的代码,并添加以下代码:@Before({"@sanity","@regression"}) public void taggedHookMethod2() { System.out.println("tagged hook - Sanity AND Regression"); } -
从 Eclipse 运行
feature文件并观察输出:![如何操作]()
代码将在具有两个标签
@sanity和@regression的 Scenario 3 之前运行。 -
要为标记为
@important但 NOT@regression的场景运行钩子代码,在Hooks.java文件中注释掉之前的代码,并添加以下代码:@Before({"@important","~@regression"}) public void taggedHookMethod3() { System.out.println("Tagged hook- important but NOT regression"); } -
从 Eclipse 运行
feature文件并观察输出:![如何操作]()
代码将在具有
@important(通过继承)且 NOT@regression标签的 Scenario 1 之前运行。 -
要为标记为
(important AND regression或(important AND wip)的场景运行钩子代码,在Hooks.java文件中注释掉之前的代码,并添加以下代码:@Before({"@important","@regression,@wip"}) public void taggedHookMethod4() { System.out.println("Tagged hook - (important+regression) OR (important AND wip)"); } -
从 Eclipse 运行
feature文件并观察输出:![如何操作]()
代码将在具有 @important(通过继承)和 @regression 标签的 Scenario 2 和 3 之前运行。
它是如何工作的…
现在我们来理解 ANDing、ORing 和 NOTing 钩子的概念。
-
OR:当提到的任何一个标签与场景相关联时,将运行钩子代码。标签以字符串形式传递,并以逗号分隔。例如:
@Before("@sanity,@wip") -
AND:当提到的所有标签都与场景相关联时,将运行钩子代码。标签作为单独的标签字符串传递。例如:
@Before({"@sanity","@regression"}) -
NOT:当提到的所有标签都没有与场景相关联时,将运行钩子代码。标签作为单独的标签字符串传递。例如:
@Before({"@important","~@regression"})
第四章:配置 Cucumber
在本章中,我们将探讨以下食谱:
-
将 Cucumber 与 JUnit 集成
-
覆盖 Cucumber 选项
-
运行严格和运行干燥
-
配置 Cucumber 控制台输出
-
将 Cucumber 输出定向到文件
-
配置命名约定
简介
当我们谈论使用 Cucumber 进行行为驱动开发时,我们经常谈论 Feature 文件、场景、背景和粘合代码(Step Definitions)。毫无疑问,在你理解前面提到的概念之前,你将无法实现 Cucumber,但一个真正重要且在日常 Cucumber 生活中非常有用的领域是配置 Cucumber。
在本章中,我们将从将 Cucumber 与 JUnit 集成开始,然后开始了解我们可以使用 @CucumberOptions 注解进行的不同配置。
将 Cucumber 与 JUnit 集成
到目前为止,我们已从 Eclipse 或终端运行 Cucumber 测试,但如何使用自动化框架与 Cucumber 一起工作?
我们如何将 Cucumber 与 JUnit 框架集成?让我们在下一节中看看。
如何做…
我们需要在 CucumberOptions 包中创建一个具有空体和 @RunWith 注解的 Java 类。这个类应该看起来像这样:
package com.CucumberOptions;
import org.junit.runner.RunWith;
import Cucumber.api.junit.Cucumber;
@RunWith(Cucumber.class)
public class RunCukeTest {
}
它是如何工作的…
Cucumber 随带一个 JUnit 运行器,Cucumber.api.junit.Cucumber 类。这个类告诉 JUnit 调用 Cucumber JUnit 运行器。它将搜索 Feature 文件并运行它们,以 JUnit 理解的格式将输出返回给 JUnit。将此类作为任何 JUnit 测试类执行将运行与该类同一包中的所有 Feature。
注意
JUnit 类的名称无关紧要,并且该类应该是空的。
覆盖 Cucumber 选项
有时,根据需求,我们可能希望覆盖默认的 Cucumber 行为,例如报告或项目结构等。我们可以通过终端配置 Cucumber,但大多数情况下我们使用 JUnit 运行 Cucumber。那么我们如何配置 Cucumber 与 JUnit 运行器,让我们在下一节中看看。
如何做…
-
将
@CucumberOptions添加到RunCuckeTest.java类中,并导入Cucumber.api.CucumberOptions。RunCukeTest.java的更新代码应该看起来像这样:package com.CucumberOptions; import org.junit.runner.RunWith; import Cucumber.api.CucumberOptions; import Cucumber.api.junit.Cucumber; @RunWith(Cucumber.class) @CucumberOptions( //your Cucumber Options code goes here ) public class RunCukeTest { } -
现在,让我们指定我们的 Feature 文件和 Step Definitions 所在的配置文件以及使用的 Tags。这是
RunCukeTest.java代码应该看起来像这样:package com.CucumberOptions; import org.junit.runner.RunWith; import Cucumber.api.CucumberOptions; import Cucumber.api.junit.Cucumber; @RunWith(Cucumber.class) @CucumberOptions( Features = "src/test/java/com/Features", glue = "com.StepDefinitions", Tags = { "~@wip","~@notImplemented","@sanity" } ) public class RunCukeTest { }参考截图以获得更多清晰度:
![如何做…]()
它是如何工作的…
@CucumberOptions 注解提供了与 Cucumber 终端行相同的选项。例如,我们可以指定 Feature 文件和 Step Definitions 的路径。
可用的不同选项如下:
| 元素 | 目的 | 默认 |
|---|---|---|
dryRun |
true(跳过粘合代码的执行) | FALSE |
strict |
true(如果有未定义或挂起的步骤,将失败执行) | FALSE |
Features |
这些是 Feature 文件的路径 | {} |
glue |
这表示在哪里查找粘合代码(步骤定义和钩子) | {} |
Tags |
这表示 Features 中哪些标签应该执行 | {} |
monochrome |
这表示是否使用单色输出 | FALSE |
plugin |
这表示声明使用哪些格式化程序(s)以及一些其他选项 | {} |
让我们了解在这个配方中我们使用的选项:
-
Features: 此选项用于指定 Feature 文件的路径。当 Cucumber 开始执行时,它会查找在 FEATURE 选项中提到的路径/文件夹中的
.Feature文件。在 FEATURE 选项中提到的路径中,具有.Feature扩展名的任何文件都将被执行。 -
Glue: GLUE 选项用于指定步骤定义和粘合代码的位置。每当 Cucumber 遇到步骤时,Cucumber 都会在 GLUE 选项中提到的文件夹中所有文件内寻找步骤定义。这也适用于钩子。
-
Tags: 此选项帮助您决定在 Features 中哪些标签应该执行,或者换句话说,哪些标签不应该执行。例如,在我们的代码中,任何被标记为
@sanity的场景都将被执行,而任何被标记为@wip的场景将不会被执行,因为@wip标签之前有~。任何标签前的~都告诉 Cucumber 跳过带有该标签的场景/Feature。注意
接受多个值的选项以列表的形式接受。在先前的表中,这些选项在默认列中已被标记为
{}。
运行严格和运行干燥
当 Cucumber 项目变得很大时,保持系统完整性变得非常重要。不应该发生添加/修改场景会破坏系统的情况。那么,如何快速检查所有步骤是否都有相关的步骤定义(而不执行这些步骤定义中的代码)?让我们在接下来的部分中了解这一点。
如何操作…
-
将
dryRun选项添加到@CucumberOptions并将其值设置为true。 -
将
strict选项添加到@CucumberOptions并将其值设置为false。 -
将
monochrome选项添加到@CucumberOptions并将其值设置为true。
这就是我们的 RunCukeTest.Java 类应该看起来像:
package com.CucumberOptions;
import org.junit.runner.RunWith;
import Cucumber.api.CucumberOptions;
import Cucumber.api.junit.Cucumber;
@RunWith(Cucumber.class)
@CucumberOptions(
Features = "src/test/java/com/Features",
glue = "com.StepDefinitions",
Tags = { "~@wip","~@notImplemented","@sanity" },
dryRun = true,
strict = false,
monochrome = true
)
public class RunCukeTest {
}
工作原理…
让我们了解 dryRun 和 chrome:
-
dryRun: 如果将
dryRun选项设置为true,Cucumber 只会检查所有步骤是否都有相应的步骤定义,并且步骤定义中提到的代码不会执行,反之亦然。这用于验证我们是否为每个步骤定义了步骤定义。考虑如果有人向项目中添加了新的场景,并希望检查他是否遗漏了任何步骤定义。他将
dryRun选项设置为true并运行所有场景。Cucumber 会检查所有场景的匹配步骤定义,而不执行步骤定义之间的代码,并返回结果。与 Cucumber 在步骤定义中执行代码相比,这种技术可以节省大量时间。 -
严格模式: 如果将
strict选项设置为false,并且在执行时 Cucumber 遇到任何未定义/挂起的步骤,那么 Cucumber 不会使执行失败,并且会跳过未定义的步骤,构建成功。控制台输出如下所示:![如何工作…]()
如果选项设置为
true,并且在执行时,如果 Cucumber 遇到任何未定义/挂起的步骤,那么 Cucumber 将使执行失败,并将未定义的步骤标记为失败。控制台输出如下所示:![如何工作…]()
-
单色模式: 如果将
monochrome选项设置为false,那么控制台输出将不如预期那样易于阅读。当monochrome选项设置为false时的输出如下所示:![如何工作…]()
当将
monochrome选项设置为true时,输出如下所示:![如何工作…]()
配置 Cucumber 控制台输出
当我们执行 Cucumber 场景时,它会在终端或 Eclipse 控制台中生成输出。该输出有一个默认行为,我们也可以根据需要配置该输出。那么我们如何修改默认行为呢?让我们在下一节中看看。
如何操作…
-
将
plugin选项添加到@CucumberOptions中,并将其值设置为{"progress"}。这是@CucumberOptions代码的示例:@CucumberOptions( Features = "src/test/java/com/Features", glue = "com.StepDefinitions", Tags = { "~@wip","~@notImplemented","@sanity" }, dryRun = false, strict = true, monochrome = true, plugin = { "progress" } ) -
如果我们现在通过终端运行场景,我们的输出将如下所示:
![如何操作…]()
-
与
progress插件相比,我们也可以使用pretty插件,它的输出比progress插件更详细。代码如下所示:@CucumberOptions( Features = "src/test/java/com/Features", glue = "com.StepDefinitions", Tags = { "~@wip","~@notImplemented","@sanity" }, dryRun = false, strict = true, monochrome = true, plugin = { "pretty" } ) -
输出如下所示:
![如何操作…]()
-
如果我们更关注每个步骤定义所花费的时间,那么我们应该使用
usage插件。这是@CucumberOptions代码的示例:@CucumberOptions( Features = "src/test/java/com/Features", glue = "com.StepDefinitions", Tags = { "~@wip","~@notImplemented","@sanity" }, dryRun = false, strict = true, monochrome = true, plugin = { "usage" } ) -
输出将如下所示:
![如何操作…]()
-
如果你期望某些场景会失败,并且想要重新运行失败的场景,那么只有在这种情况下才使用
Rerun插件。这是@CucumberOptions代码的示例:@CucumberOptions( Features = "src/test/java/com/Features", glue = "com.StepDefinitions", Tags = { "~@wip","~@notImplemented","@sanity" }, dryRun = false, strict = true, monochrome = true, plugin = { "rerun" } ) -
输出如下所示:
![如何操作…]()
工作原理
让我们了解在前面的步骤中使用到的不同插件:
-
进度:这是 Cucumber 的默认插件,每个步骤产生一个字符。每个字符代表每个步骤的状态:
-
.表示通过 -
U表示未定义 -
-表示跳过(或场景概述步骤) -
F表示失败
-
-
pretty:这是一个更详细的插件,包含有关哪个步骤匹配哪个步骤定义、参数和步骤位置等信息。
-
用法:这将按平均执行时间对步骤定义进行排序。
用法插件的输出对于快速找到代码中的慢速部分非常有用,同时也是获取步骤定义概览的好方法。 -
重试:此插件输出失败的场景位置,以便可以直接用于下一次执行。如果所有场景都通过,则
重试插件不会产生任何内容。
将 Cucumber 输出定向到文件
Cucumber 将业务逻辑与代码集成,因此我们的重点是业务而不是代码。相同的理念也体现在 Cucumber 报告中。Cucumber 报告更多地关注业务实用性,而不是与更多图表相关。
强大的自动化框架生成非常好的详细报告,可以与所有利益相关者共享。根据需求有多种报告选项可供使用。让我们在下一节中查看如何广泛使用报告。
如何做…
-
对于 HTML 报告,请在
@CucumberOptions插件选项中添加html:target/Cucumber。以下是@CucumberOptions代码的示例:@CucumberOptions( Features = "src/test/java/com/Features", glue = "com.StepDefinitions", Tags = { "~@wip","~@notImplemented","@sanity" }, dryRun = false, strict = true, monochrome = true, plugin = { "progress", "html:target/Cucumber" } ) -
对于 JSON 报告,请在
@CucumberOptions插件选项中添加json:target_json/Cucumber.json。以下是@CucumberOptions代码的示例:@CucumberOptions( Features = "src/test/java/com/Features", glue = "com.StepDefinitions", Tags = { "~@wip","~@notImplemented","@sanity" }, dryRun = false, strict = true, monochrome = true, plugin = { "pretty", "json:target_json/Cucumber.json" } ) -
对于 JUnit 报告,请在
@CucumberOptions插件选项中添加junit:target_json/Cucumber_junit.xml。以下是@CucumberOptions代码的示例:@CucumberOptions( Features = "src/test/java/com/Features", glue = "com.StepDefinitions", Tags = { "~@wip","~@notImplemented","@sanity" }, dryRun = false, strict = true, monochrome = true, plugin = { "pretty", "junit:target_json/Cucumber_junit.xml" } )
如何工作…
默认情况下,插件输出被重定向到 STDOUT,如果我们想将输出存储到文件格式,我们需要将输出重定向到该文件。语法如下:
FORMAT : <<filepath>>
让我们详细了解每个输出:
-
HTML:这将生成一个位于格式化器本身提到的位置的 HTML 报告。以下是 HTML 文件的外观:
![如何工作…]()
-
JSON:此报告包含来自 Gherkin 源的 JSON 格式的所有信息。此报告旨在通过第三方工具(如 Cucumber Jenkins)进行后处理,转换为另一种可视化格式。以下是 JSON 报告的外观:
![如何工作…]()
-
JUnit:此报告生成与 Apache Ant 的
junitreport任务类似的 XML 文件。这种 XML 格式被大多数持续集成服务器理解,它们将使用它来生成可视化报告。以下是 JUnit 报告的外观:![如何工作…]()
配置命名约定
由于 Cucumber 可以用多种语言实现,具有多种语言知识和背景的开发者可以在同一个项目上工作。因此,有时团队可能难以管理命名约定,例如下划线或驼峰命名法。
Cucumber 允许团队选择命名约定。根据命名约定,Cucumber 为步骤定义生成方法名。让我们在下一节看看它是如何实现的。
如何做…
-
如果您想使用驼峰命名法,那么请将以下代码添加到
@CucumberOptions中:snippets=SnippetType.CAMELCASE -
如果您想使用下划线,那么请将以下代码添加到
@CucumberOptions中。snippets=SnippetType.UNDERSCORE -
这就是
RunCukeTest.Java代码的样子:package com.CucumberOptions; import org.junit.runner.RunWith; import Cucumber.api.CucumberOptions; import Cucumber.api.SnippetType; import Cucumber.api.junit.Cucumber; @RunWith(Cucumber.class) @CucumberOptions( Features = "src/test/java/com/Features", glue = "com.StepDefinitions", Tags = { "~@wip","~@notImplemented","@sanity" }, dryRun = false, strict = true, monochrome = true, plugin = { "pretty", "junit:target_junit/Cucumber.xml" }, Snippets = SnippetType.CAMELCASE ) public class RunCukeTest { }
如何工作…
让我们详细了解一下Snippets选项:
片段:此选项是键入的,因此您需要使用提供的其中一个常量,即SnippetType.CAMELCASE或SnippetType.UNDERSCORE。请记住导入Cucumber.api.SnippetType。默认选项是下划线。如果我们选择驼峰命名法,步骤定义的建议如下所示:

第五章。运行 Cucumber
在本章中,我们将介绍以下食谱:
-
将 Cucumber 与 Maven 集成
-
从终端运行 Cucumber
-
从终端覆盖选项
-
将 Cucumber 与 Jenkins 和 GitHub 集成
-
并行运行 Cucumber 测试用例
简介
为了成功实施任何测试框架,强制要求测试用例能够以多种方式运行,以便不同能力水平的人能够按需使用。因此,现在我们将关注运行 Cucumber 测试用例。运行 Cucumber 的方式有多种,例如将其与 Maven 集成并在终端中运行;我们还可以通过将 Cucumber 与 Jenkins 集成来远程运行 Cucumber。
在本章中,我们还将介绍如何通过 Cucumber、Maven 和 JUnit 的组合并行运行 Cucumber 测试用例的高级主题。
将 Cucumber 与 Maven 集成
Maven 相比其他构建工具有很多优势,例如依赖项管理、众多插件以及运行集成测试的便利性。因此,让我们也将我们的框架与 Maven 集成。Maven 将允许我们的测试用例以不同的方式运行,例如从终端运行、与 Jenkins 集成以及并行执行。
我们如何与 Maven 集成?让我们在下一节中找出答案。
准备工作
我假设我们已经了解了 Maven 的基础知识(Maven 的基础知识不在此书的范围之内)。请按照以下说明在您的系统上安装 Maven 并创建一个示例 Maven 项目。
-
我们首先需要在我们的系统上安装 Maven。因此,请遵循以下博客上的说明:
对于 Windows 系统:
www.mkyong.com/maven/how-to-install-maven-in-windows/对于 Mac 系统:
-
我们还可以按照本博客上提到的说明安装 Maven Eclipse 插件:
theopentutorials.com/tutorials/eclipse/installing-m2eclipse-maven-plugin-for-eclipse/ -
要将 Maven 项目导入 Eclipse,请遵循本博客上的说明:
如何实现...
由于这是一个 Maven 项目,我们将更改 pom.xml 文件以添加 Cucumber 依赖项。
-
首先,我们将声明一些自定义属性,这些属性将用于管理依赖项版本:
<properties> <junit.version>4.11</junit.version> <cucumber.version>1.2.2</cucumber.version> <selenium.version>2.45.0</selenium.version> <maven.compiler.version>2.3.2</maven.compiler.version> </properties> -
现在,我们将添加 Cucumber-JVM 的依赖项,其作用域为测试:
<!—- Cucumber-java--> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-java</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency> -
现在我们需要添加 Cucumber-JUnit 的依赖项,其作用域为测试。
<!-— Cucumber-JUnit --> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-junit</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency>就这样!我们已经集成了 Cucumber 和 Maven。
它是如何工作的...
通过遵循这些步骤,我们已经创建了一个 Maven 项目并添加了 Cucumber-Java 依赖项。目前,这个项目只有一个 pom.xml 文件,但这个项目可以用于添加不同的模块,例如特性文件和步骤定义。
使用属性的优势在于我们确保依赖项版本在 pom.xml 文件的一个地方声明。否则,我们会在多个地方声明依赖项,可能会导致依赖项版本不一致。
Cucumber-Java 依赖项是 Cucumber 不同构建块所需的主要依赖项。Cucumber-JUnit 依赖项用于 Cucumber JUnit 运行器,我们在运行 Cucumber 测试用例时使用它。
从终端运行 Cucumber
现在我们已经将 Cucumber 与 Maven 集成,从终端运行 Cucumber 将不会是问题。从终端运行任何测试框架都有其自身优势,例如覆盖代码中提到的运行配置。
那么,我们如何在终端运行 Cucumber 测试用例呢?让我们在下一节中找出答案。
如何操作…
-
打开命令提示符并
cd到项目根目录。 -
首先,让我们从命令提示符中运行所有 Cucumber 场景。由于这是一个 Maven 项目,并且我们已经添加了 Cucumber 测试范围依赖项,并且所有特性也添加到了测试包中,请在命令提示符中运行以下命令:
mvn test这是输出:
![如何操作…]()
![如何操作…]()
-
之前的命令运行了 JUnit 运行器类中提到的所有内容。然而,如果我们想覆盖运行器中提到的配置,则需要使用以下命令:
mvn test –DCucumber.options="<<OPTIONS>>" -
如果你需要这些 Cucumber 选项的帮助,请在命令提示符中输入以下命令并查看输出:
mvn test -Dcucumber.options="--help"这是输出:
![如何操作…]()
如何工作…
mvn test 使用 Cucumber 的 JUnit 运行器运行 Cucumber 特性。RunCukesTest 类上的 @RunWith (Cucumber.class) 注解告诉 JUnit 启动 Cucumber。Cucumber 运行时解析命令行选项以确定要运行哪个特性、粘合代码的位置、要使用的插件等。当你使用 JUnit 运行器时,这些选项是从你的测试上的 @CucumberOptions 注解生成的。
从终端覆盖选项
当需要覆盖 JUnit 运行器中提到的选项时,我们需要从终端使用 Dcucumber.options。让我们看看一些实际例子。
如何操作…
-
如果我们想通过指定文件系统路径来运行场景,请运行以下命令并查看输出:
mvn test -Dcucumber.options="src/test/java/com/features/sample.feature:5"![如何操作…]()
在前面的代码中,"
5" 是特性文件中场景开始的行号。 -
如果我们想通过标签运行测试用例,那么我们运行以下命令并注意输出:
mvn test -Dcucumber.options="--tags @sanity"以下是指令的输出:
![如何操作…]()
-
如果我们想要生成不同的报告,可以使用以下命令,并查看在指定位置生成的 JUnit 报告:
mvn test -Dcucumber.options="--plugin junit:target/cucumber-junit-report.xml"![如何操作…]()
工作原理…
当您使用 -Dcucumber.options 覆盖选项时,您将完全覆盖在您的 @CucumberOptions 中硬编码的任何选项。此规则有一个例外,那就是 --plugin 选项。它不会覆盖,而是会添加一个插件。
将 Cucumber 与 Jenkins 和 GitHub 集成
要远程调度测试用例执行,我们将我们的测试框架与 Jenkins 集成。Jenkins 作为开源、免费、易于使用,可以按计划时间运行或触发事件后的构建等,具有许多优点。因此,我们的 Cucumber 测试用例也应在 Jenkins 上运行变得非常重要。我们将在下一章中详细探讨这一点。
准备工作
-
在本地机器上安装和运行 Jenkins 不在本书的讨论范围内。我假设您已经有一个本地或远程 Jenkins 设置就绪,并且可以通过 URL 访问 Jenkins。如果您需要有关设置的任何帮助,请遵循博客中提到的步骤
wiki.jenkins-ci.org/display/JENKINS/Installing+Jenkins。 -
我们还需要将我们的项目上传到 GitHub。再次提醒,将项目提交到 GitHub 不在本书的讨论范围内,但如果您需要任何帮助,可以参考
help.github.com/articles/set-up-git/中提到的说明。 -
GitHub 项目 URL 是
github.com/ShankarGarg/CucumberBook.git,可以用来下载我们至今一直在使用的项目。
如何操作…
-
在任何浏览器中通过 URL
http://localhost:8080/jenkins/打开 Jenkins(如果您本地没有运行 Jenkins,请将 localhost 替换为机器的 IP 地址)。 -
前往 Jenkins 仪表板,点击 新建项目:
![如何操作…]()
-
输入我们想要创建的作业的名称,并选择 构建 Maven 项目 选项。点击 确定。
![如何操作…]()
-
现在,输入项目的 描述:
![如何操作…]()
-
然后,通过在 源代码管理 中选择 Git 选项并输入 仓库 URL
github.com/ShankarGarg/CucumberBook.git和 凭据 来输入 Git 详细信息。保持本节中的其他选项为默认设置。![如何操作…]()
-
现在,我们来到 构建 部分。由于我们选择了 Maven 项目,
Root POM会自动显示。我们需要指定我们想要运行的 goal,在我们的例子中是test:![如何操作…]()
-
保持所有其他选项为默认设置,并点击 保存。
-
现在,点击立即构建来运行项目。
![如何做到这一点…]()
-
一旦你点击立即构建,就会立即触发构建。你可以看到构建号和时间戳。
![如何做到这一点…]()
-
点击构建上的时间戳。然后点击控制台输出。只是验证输出以确保测试用例是否已运行:
![如何做到这一点…]()
它是如何工作的...
由于我们已经将 Maven 与 Cucumber 集成,Jenkins 的集成基本上就是使用 Jenkins 运行 Maven 项目。Jenkins 默认自带 Maven 插件(当我们选择项目类型为构建 Maven 项目时,大多数设置都在那时处理好了)构建部分预先填充了pom.xml,我们只需提及目标为测试。
Jenkins 也预先安装了 GitHub 插件,我们只需提及 GitHub URL 和凭证。因此,每次构建项目时,Jenkins 都会从 GitHub 获取最新代码,然后运行测试用例。
并行运行 Cucumber 测试用例
并行运行测试用例是良好自动化框架的一个非常常见且必需的实践。默认情况下,Cucumber 没有这样的选项或设置。然而,由于 Cucumber 可以与 Maven 和 JUnit 集成,使用这两个工具,我们可以并行运行 Cucumber 情景。
在这个菜谱中,我们将并行运行两个情景,对于网络自动化,这意味着同时打开两个浏览器。那么我们如何实现这一点呢?让我们在下一节中了解。
如何做到这一点...
-
我们创建了一个包含两个情景的特征文件。我们的目标是并行运行这两个情景。这只是为了演示目的,你可以为n个情景实现相同的方法。
这两个情景都将与两个不同的标签相关联,以便可以使用这些标签运行。关注以下代码中高亮显示的标签;我们的特征文件应该看起来像这样:
Feature: sample @sanity Scenario: Home Page Default content Given user is on Application home page Then user gets a GitHub bootcamp section @regression Scenario: GitHub Bootcamp Section Given user is on Application home page When user focuses on GitHub Bootcamp Section Then user gets an option to setup git -
现在,我们也应该为这个特征文件准备好步骤定义。为了专注于这个菜谱的主要目标,我将代码保持在步骤定义中的占位符。以下是我们步骤定义的样子:
@Given("^user is on Application home page$") public void user_is_on_Application_home_page() { System.out.println("application home"); } @Then("^user gets a GitHub bootcamp section$") public void user_gets_a_ GitHub_bootcamp_section() { System.out.println("bootcamp section"); } @When("^user focuses on GitHub Bootcamp Section$") public void user_focuses_on_GitHub_Bootcamp_Section() { System.out.println("focus on GitHub"); } @Then("^user gets an option to setup git$") public void user_gets_an_option_to_setup_git() { System.out.println("git section"); } -
下一步是编写运行者,以确保测试用例并行运行。在这个方向上的第一步是有一个
RunCukeTest.java类,它将专门运行与@sanity标签关联的情景。RunCukeTest的代码如下:package com.CucumberOptions; import org.junit.runner.RunWith; import cucumber.api.CucumberOptions; import cucumber.api.junit.Cucumber; @RunWith(Cucumber.class) @CucumberOptions( features = "src/test/java/com/features", glue = "com.StepDefinitions", tags = { "@sanity" }, dryRun = false, strict = true, monochrome = true, plugin = { "pretty", "html:target/cucumber_sanity.html" } ) public class RunCukeTest { } -
现在,我们需要再写一个运行标记为
@regression的情景的运行者。关注以下代码中高亮的部分;RunCukeParallelTest.java的代码将如下所示:package com.CucumberOptions; import org.junit.runner.RunWith; import cucumber.api.CucumberOptions; import cucumber.api.junit.Cucumber; @RunWith(Cucumber.class) @CucumberOptions( features = "src/test/java/com/features", glue = "com.StepDefinitions", tags = { "@regression" }, dryRun = false, strict = true, monochrome=true, plugin = { "pretty", "html:target/cucumber_regression.html" } ) public class RunCukeParallelTest { } -
最后一步是确保这两个 Runner 类并行运行。我们将通过修改
pom.xml文件来实现这一点。我们将添加 Maven Surefire 插件的配置,这将使 Runner 类并行运行。以下是我们要添加到现有pom.xml文件中的内容:<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.5.1</version> <configuration> <encoding>UTF-8</encoding> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.14</version> <configuration> <skip>true</skip> </configuration> <executions> <execution> <id>acceptance-test</id> <phase>integration-test</phase> <goals> <goal>test</goal> </goals> <configuration> <skip>false</skip> <forkCount>2</forkCount> <reuseForks>false</reuseForks> <argLine>-Duser.language=en</argLine> <argLine>-Xmx1024m</argLine> <argLine>-XX:MaxPermSize=256m</argLine> <argLine>-Dfile.encoding=UTF-8</argLine> <useFile>false</useFile> <includes> <include>**/*Test.class</include> </includes> <testFailureIgnore>true</testFailureIgnore> </configuration> </execution> </executions> </plugin> </plugins> </build> -
我们还将添加 Selenium 的依赖项,以便我们可以编写调用 Selenium Webdriver 实例的代码。这段代码如下:
<!-- Selenium --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-firefox-driver</artifactId> <version>${selenium.version}</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-support</artifactId> <version>${selenium.version}</version> </dependency> -
我们将创建一个名为
DriverFactory.java的另一个类,用于添加 Selenium 代码,它将有一个调用 Firefox 浏览器的函数。这个类看起来是这样的:package com.cucumber.automation.utils; import java.net.MalformedURLException; import java.util.concurrent.TimeUnit; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.support.ui.WebDriverWait; public class DriverFactory { public static WebDriver driver = null; public static WebDriverWait waitVar = null; public static String baseURL = "https://github.com/"; /** * This function is to invoke Selenium Webdriver * * @throws MalformedURLException * @throws InterruptedException */ public void createDriver() throws MalformedURLException, InterruptedException { driver = new FirefoxDriver(); driver.manage().window().maximize(); driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS); driver.get(baseURL); waitVar = new WebDriverWait(driver, 15); } /** * This function is to close driver instance */ public void teardown() { driver.quit(); } } -
我们将更新
hooks.java文件,添加createDriver()函数,以便在每个 Scenario 之前调用 Firefox。代码将如下所示:@Before public void taggedHookMethod1() throws InterruptedException { System.out.println("inside hook"); DriverFactory.createDriver(); } -
打开终端,使用
cd命令进入项目根目录。要调用我们放在pom.xml文件中的 Maven 配置,请运行以下命令:mvn integration-test这是前面命令的输出:
![如何做到这一点…]()
它是如何工作的…
Maven surefire 插件将根据配置标签中的forkcount标签值运行配置标签中提到的include标签中的类。所以所有以Test结尾的 Java 类(即我们的RunCukeTest和RunCukeParallelTest JUnit Runner 类)将在不同的线程中并行执行。由于这两个类是 JUnit Runner 类,它们将调用带有@sanity标签的 Scenarios 的执行,并在不同的线程上执行带有@regression标签的 Scenarios。
第六章:构建 Cucumber 框架
在本章中,我们将介绍以下食谱:
-
构建 Web 自动化框架
-
构建 Mobile 自动化框架
-
构建 REST 自动化框架
简介
Cucumber 是一个实现 BDD 的平台,而不是自动化应用程序。对于自动化应用程序,有各种 API 可用,如用于 Web 的 Selenium Webdriver、用于移动的 Appium 和用于 REST 服务的 HTTP Client。到目前为止,我们已经学习了各种 Cucumber 功能,以便我们可以构建一个健壮的自动化框架,但我们还需要将 Cucumber 与前面提到的 API 集成,以便我们可以拥有自动化实时应用程序的框架。
在本章中,我们将学习如何构建自动化 Web、REST 和移动应用程序的框架。
构建 Web 自动化框架
Selenium 是一个 Web 自动化工具,由于其功能和力量,它让测试人员的生活变得更加容易,并且是测试人员自动化网站的首选。这就是我们为什么也选择了 Selenium 作为我们的框架。
当我们创建一个框架时,我们应该记住添加新的测试用例和修改现有测试用例应该是相对简单和容易的。
让我们学习如何使用 Cucumber 和 Selenium 创建一个健壮的 Web 自动化框架,同时考虑到可维护性和可扩展性。
准备工作
根据最新 Selenium 版本支持的版本,在您的系统上安装 Firefox。有关 Selenium 和浏览器支持的更多信息,请访问 docs.seleniumhq.org/about/platforms.jsp 的网页。
如何操作…
让我们先了解在这个框架中我们将使用的测试用例。我们将测试 github.com/ 的登录功能。以下是测试用例流程:
-
我们打开
github.com/并点击 登录。 -
我们没有输入用户名和密码,然后点击 登录:
![如何操作…]()
-
我们验证我们得到的错误信息:
![如何操作…]()
-
我们将使用 Eclipse Maven 插件创建一个简单的 Maven 项目。在 Eclipse 菜单中点击 新建,然后按照截图创建名为
CucumberWebAutomation的 Maven 项目:![如何操作…]()
![如何操作…]()
![如何操作…]()
-
由于这是一个 Maven 项目,我们将更新
pom.xml文件以添加 Cucumber 和 Selenium 的依赖项。目前,pom.xml的样子如下:<project 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>CucumberWebAutomation</groupId> <artifactId>CucumberWebAutomation</artifactId> <version>0.0.1-SNAPSHOT</version> <name>CucumberWebAutomation</name> <description>Cucumber+Selenium project</description> </project> -
我们将添加
properties标签并定义 Cucumber 和 Selenium 的版本属性,这样当我们需要更新依赖项版本时,我们只需在一个地方进行操作。将以下代码添加到<project>标签内的pom.xml中:<properties> <selenium.version>2.45.0</selenium.version> <cucumber.version>1.2.2</cucumber.version> </properties>注意
要检查 Maven 中央仓库上的最新依赖项版本,请参阅
search.maven.org/。 -
我们将为 Cucumber-JVM(BDD)和 Selenium-JVM(网络自动化)添加依赖。请在
pom.xml文件中的</properties>标签之后添加以下代码:<dependencies> <!-- cucumber --> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-java</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-junit</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency> <!-- Selenium --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>${selenium.version}</version> </dependency> </dependencies> -
我们将创建项目结构,并将相似的文件保存在同一个包中。按照此截图所示的步骤创建所需的包:
![如何操作…]()
-
由于这是一个 Cucumber 项目,我们将添加
RunCukeTest.java文件来指定配置,例如特性文件的位置、步骤定义的位置、输出位置等。将以下类添加到CucumberOptions包中:package com.cucumber.automation.bdt.cucumberOptions; import org.junit.runner.RunWith; import cucumber.api.CucumberOptions; import cucumber.api.junit.Cucumber; @RunWith(Cucumber.class) @CucumberOptions( features = "src/test/java/com/cucumber/automation/bdt/features", glue = "com.cucumber.automation.bdt.stepDefinitions", plugin = { "pretty", "html:target/cucumber", } ) public class RunCukeTest { } -
现在是时候添加一个特性文件来指定需求了。我们将把
github_login.feature文件添加到features包中。这是我们的特性文件的样子:Feature: login Page In order to test login page As a Registered user I want to specify the login conditions Scenario: login without username and password Given user is on github homepage When user clicks on Sign in button Then user is displayed login screen When user clicks Sign in button Then user gets an error message "Incorrect username or password." -
下一步是添加步骤定义。最简单的方法是运行一次项目,并使用 Cucumber 给出的建议。在
stepDefinitions包中添加一个名为GithubLoginSD.java的文件,并包含以下代码:package com.cucumber.automation.bdt.stepDefinitions; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; public class GithubLoginSD { @Given("^user is on github homepage$") public void user_is_on_github_homepage() { } @When("^user clicks on Sign in button$") public void user_clicks_on_Sign_in_button() { } @Then("^user is displayed login screen$") public void user_is_displayed_login_screen() { } @When("^user clicks Sign in button$") public void user_clicks_Sign_in_button() { } @Then("^user gets an error message \"(.*?)\"$") public void user_gets_an_error_message(String arg1) { } } -
现在我们需要添加 Selenium 函数,这些函数可以在执行完成后调用 Firefox 浏览器并关闭浏览器。出于简单起见,我将保持此文件非常基础。在
utils包中创建DriverFactory.java类,代码应该如下所示:package com.cucumber.automation.utils; import java.net.MalformedURLException; import java.util.concurrent.TimeUnit; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.support.ui.WebDriverWait; public class DriverFactory { public static WebDriver driver = null; public static WebDriverWait waitVar = null; public static String baseURL = "https://github.com/"; /** * This function is to invoke Selenium Webdriver * * @throws MalformedURLException * @throws InterruptedException */ public void createDriver() throws MalformedURLException, InterruptedException { driver = new FirefoxDriver(); driver.manage().window().maximize(); driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS); driver.get(baseURL); waitVar = new WebDriverWait(driver, 15); } /** * This function is to close driver instance */ public void teardown() { driver.quit(); } } -
现在我们需要挂钩此代码,以便在每个场景之前启动浏览器,在每个场景之后关闭浏览器。在
stepdefinitions包中创建一个名为Hooks.java的文件,并包含以下代码:package com.cucumber.automation.bdt.stepDefinitions; import java.net.MalformedURLException; import com.cucumber.automation.utils.DriverFactory; import cucumber.api.java.After; import cucumber.api.java.Before; public class Hooks { DriverFactory df = new DriverFactory(); @Before public void beforeScenario() throws MalformedURLException, InterruptedException{ df.createDriver(); } @After public void afterScenario(){ df.teardown(); } } -
现在我们将实现页面对象模型(POM),这样维护和扩展 Selenium 代码将会相对简单。我也将 POM 保持得相对简单;你可以在你的项目中自由扩展它。
我们将在
web.pages包中添加 GitHub 登录页面的page对象,代码将如下所示:package com.cucumber.automation.web.pages; import static org.junit.Assert.assertEquals; import org.openqa.selenium.By; import org.openqa.selenium.support.ui.ExpectedConditions; import com.cucumber.automation.utils.DriverFactory; public class LoginPage extends DriverFactory { /** * All locators will be mentioned here * * For this example i am not using properties file for reading locators */ By SigninLink = By.linkText("Sign in"); By marketingSection = By.className("marketing-section-signup"); By loginSection = By.className("auth-form-body"); By SigninButton = By.name("commit"); By errorMessage = By.xpath("//div[@id='site-container']/div/div"); /** * All functions related to behavior will follow now */ public void ishomepageDisplayed() { waitVar.until(ExpectedConditions.presenceOfElementLocated(SigninLink)); driver.findElement(SigninLink).isDisplayed(); driver.findElement(marketingSection).isDisplayed(); } public void clickSigninLink() { driver.findElement(SigninLink).click(); } public void isloginsectionDisplayed() { waitVar.until(ExpectedConditions.presenceOfElementLocated(loginSection)); waitVar.until(ExpectedConditions.presenceOfElementLocated(SigninButton)); } public void clickSigninButton() { driver.findElement(SigninButton).click(); } public void verifyErrorMessage(String msg) { waitVar.until(ExpectedConditions.presenceOfElementLocated(errorMessage)); assertEquals(msg, driver.findElement(errorMessage).getText()); } } -
我们将不得不更新我们刚刚编写的 Selenium 函数的步骤定义文件。添加所有函数后,代码应该看起来像这样:
package com.cucumber.automation.bdt.stepDefinitions; import com.cucumber.automation.web.pages.LoginPage; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; public class GithubLoginSD { LoginPage lp = new LoginPage(); @Given("^user is on github homepage$") public void user_is_on_github_homepage() { lp.ishomepageDisplayed(); } @When("^user clicks on Sign in button$") public void user_clicks_on_Sign_in_button() { lp.clickSigninLink(); } @Then("^user is displayed login screen$") public void user_is_displayed_login_screen() { lp.isloginsectionDisplayed(); } @When("^user clicks Sign in button$") public void user_clicks_Sign_in_button() { lp.clickSigninButton(); } @Then("^user gets an error message \"(.*?)\"$") public void user_gets_an_error_message(String arg1) { lp.verifyErrorMessage(arg1); } } -
我们的框架已经准备好执行。我们可以从 Eclipse 或命令行运行此框架。让我们使用
mvn test从命令行运行它。以下是将显示的输出:![如何操作…]()
它是如何工作的…
我们已经将 Cucumber、Selenium、Maven、Java 和页面对象集成到我们的 Web 自动化框架设计中。Cucumber 用于实现 BDD,这样非技术人员也可以直接参与到开发中来——Selenium 用于网络自动化,Java 作为编程语言,Maven 作为构建工具。
页面对象是一种框架设计方法,用于维护和访问分散在测试场景中的组件和控制。页面对象为我们的测试创建了一个领域特定语言(DSL),这样如果页面上有东西发生变化,我们不需要更改测试;我们只需要更新代表页面的对象。
构建移动自动化框架
Appium 是一个开源的移动自动化工具,它使测试人员的生活变得更加容易,因为它支持 Android 和 iOS。它扩展了 Selenium API,因此所有 Selenium 的优势加上在多个平台上运行测试用例的优势,使其成为移动自动化的明显选择。
让我们学习如何使用 Cucumber 和 Appium 创建一个健壮的移动自动化框架,同时考虑到使用页面对象模型来保持可维护性和可扩展性。
我只是演示 Android 应用程序自动化;相同的项目和框架也可以用于 iOS 自动化。如果需要,我还会为 iOS 包创建占位符供您参考,以便您可以使用单个框架同时为 Android 和 iOS 应用程序进行自动化。
准备工作
-
在您的系统上下载并安装 Appium,更多信息请参阅
appium.io/downloads.html。 -
从
developer.android.com/tools/help/sdk-manager.html下载并安装 Android SDK Manager。 -
从
developer.android.com/tools/help/avd-manager.html下载并安装 Android AVD Manager。 -
创建一个 Android 虚拟设备来运行应用程序。更多信息请参阅
developer.android.com/tools/devices/managing-avds.html。注意
在 Windows 上运行 Android 或在 Appium 上运行测试用例超出了本书的范围。我们期望读者具备基本的移动自动化知识。我们将专注于创建 Cucumber Appium 框架。
如何操作…
让我们首先了解在这个框架中我们将使用的测试用例。我们将使用 Android 的 Agile NCR 应用程序来完成这个菜谱。让我一步步说明测试步骤:
-
我们打开 Agile NCR 应用程序并验证主页:
![如何操作…]()
-
我们点击 议程 选项并验证 议程 屏幕:
![如何操作…]()
-
我们点击后退按钮并验证主页。
注意
我已经将此应用程序包含在可在 GitHub 上找到的项目中。
-
我们将使用 Eclipse Maven 插件创建一个简单的 Maven 项目。使用第一道菜中提到的相同步骤创建项目,并将其命名为
CucumberMobileAutomation。 -
由于这是一个 Maven 项目,我们将更新
pom.xml文件以添加 Cucumber 和 Appium 的依赖项。到目前为止,pom.xml文件看起来是这样的:<project 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>CucumberMobileAutomation</groupId> <artifactId>CucumberMobileAutomation</artifactId> <version>0.0.1-SNAPSHOT</version> <name>CucumberMobileAutomation</name> <description>Cucumber+Appium project</description> </project> -
我们将添加
properties标签并定义 Cucumber 和 Appium 的版本属性,这样当我们需要更新依赖项版本时,我们只需在一个地方进行操作。将以下代码添加到pom.xml文件中:<properties> <appium.version>2.2.0</appium.version> <cucumber.version>1.2.2</cucumber.version> </properties>注意
要检查 Maven 中央仓库上的最新依赖版本,请参阅
search.maven.org/。 -
我们将为 Cucumber-JVM 添加依赖以实现 BDD,并为移动自动化添加 Appium-Java。将以下代码添加到
pom.xml中:<dependencies> <!-- cucumber --> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-java</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-junit</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency> <!-- Appium --> <dependency> <groupId>io.appium</groupId> <artifactId>java-client</artifactId> <version>${appium.version}</version> </dependency> </dependencies> -
我们将创建项目结构,并将类似文件保存在相同的包中。我将使用为 Android 添加的包,并将为 iOS 添加的包作为参考。按照此截图所示的步骤操作,创建以下包:
![如何操作…]()
-
由于这是一个 Cucumber 项目,我们将添加
RunCukeTest.java文件来指定配置,例如 Feature 文件的位置、步骤定义的位置和输出位置等。将以下类添加到CucumberOptions包中:package com.cucumber.automation.bdt.cucumberOptions; import org.junit.runner.RunWith; import cucumber.api.CucumberOptions; import cucumber.api.junit.Cucumber; @RunWith(Cucumber.class) @CucumberOptions( features = "src/test/java/com/cucumber/automation/bdt/features", glue = "com.cucumber.automation.bdt.stepDefinitions", plugin = { "pretty", "html:target/cucumber", } ) public class RunCukeTest { } -
现在是时候添加一个特性文件来指定需求了。我们将添加
agile_ncr.feature文件到features包中。这是我们的特性文件的样子:Feature: Agile NCR App In order to look at Agile NCR Conference As a Registered user I want to specify the flow to Agenda and Speakers Scenario: Agenda Given user is on AgileNCR Home Page Then user gets an option Agenda When user selects Agenda Then user is on Agenda Screen When user chooses to go back Then user is on AgileNCR Home Page -
下一步是添加步骤定义。最简单的方法是运行一次项目并使用 Cucumber 提供的建议。将文件
AgileNCRSD.java添加到stepDefinitions包中,它应该包含以下代码:package com.cucumber.automation.bdt.stepDefinitions; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; public class AgileNCRSD { @Given("^user is on AgileNCR Home Page$") public void user_is_on_AgileNCR_Home_Page() { } @Then("^user gets an option Agenda$") public void user_gets_an_option_Agenda() { } @When("^user selects Agenda$") public void user_selects_Agenda() { } @Then("^user is on Agenda Screen$") public void user_is_on_Agenda_Screen() { } @When("^user chooses to go back$") public void user_chooses_to_go_back() { } } -
现在,将运行
app所需的.apk文件添加到src/test/resources中的 apps 文件夹。 -
然后,我们需要添加 Appium 功能,这些功能可以用来调用 Android 应用并在执行完成后关闭应用。为了简化,我保持这个文件非常基础。在
utils包中创建AppiumFactory.java类,代码应该如下所示:package com.cucumber.automation.utils; import io.appium.java_client.android.AndroidDriver; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.support.ui.WebDriverWait; public class AppiumFactory { public static AndroidDriver driver = null; public static WebDriverWait waitVar = null; /** * This function is to invoke Appium * * @throws MalformedURLException * @throws InterruptedException */ public void createDriver() throws MalformedURLException { // set up appium final File classpathRoot = new File(System.getProperty("user.dir")); final File appDir = new File(classpathRoot, "src/test/resources/apps"); final File app = new File(appDir, "com.xebia.eventsapp_2.1.apk"); final DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability("platformName", "Android"); capabilities.setCapability("deviceName", "Android Emulator"); capabilities.setCapability("platformVersion", "4.4"); capabilities.setCapability("app", app.getAbsolutePath()); driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); waitVar = new WebDriverWait(driver, 90); } /** * This function is to close driver instance */ public void teardown() { driver.quit(); } } -
现在,我们需要挂钩此代码,以便在每个场景之前启动浏览器,在每个场景之后关闭浏览器。在
stepdefinitions包中创建一个Hooks.java文件,包含以下代码:package com.cucumber.automation.bdt.stepDefinitions; import java.net.MalformedURLException; import com.cucumber.automation.utils.AppiumFactory; import cucumber.api.java.After; import cucumber.api.java.Before; public class Hooks { AppiumFactory df = new AppiumFactory(); @Before public void beforeScenario() throws MalformedURLException, InterruptedException{ df.createDriver(); } @After public void afterScenario(){ df.teardown(); } } -
现在,我们将实现 POM,这样维护和扩展 Appium 代码将会相对简单。我也将 POM 保持得相对简单;你可以在你的项目中自由扩展它。
我们将添加两个页面对象,一个用于
HomePage,另一个用于AgendaPage。HomePage.java的代码如下所示:package com.cucumber.automation.mobile.pages.android; import static org.junit.Assert.assertTrue; import org.openqa.selenium.By; import org.openqa.selenium.support.ui.ExpectedConditions; import com.cucumber.automation.utils.AppiumFactory; public class HomePage extends AppiumFactory { /** * All locators will be mentioned here * * For this example i am not using properties file for reading locators */ By homePageImage = By.id("com.xebia.eventsapp:id/home_banner_imageView"); By agendaButton = By.id("com.xebia.eventsapp:id/home_agenda_title"); By backButton = By.id("android:id/home"); /** * All functions related to behavior will follow now */ public void verifyHomePage(){ waitVar.until(ExpectedConditions.presenceOfElementLocated(homePageImage)); assertTrue(driver.findElement(homePageImage).isDisplayed()); } public void verifyHomePageOptions(){ waitVar.until(ExpectedConditions.presenceOfElementLocated(homePageImage)); waitVar.until(ExpectedConditions.elementToBeClickable(agendaButton)); assertTrue(driver.findElement(agendaButton).isDisplayed()); } public void clickAgenda(){ driver.findElement(agendaButton).click(); } }AgendaPage.java的代码如下所示:package com.cucumber.automation.mobile.pages.android; import static org.junit.Assert.*; import org.openqa.selenium.By; import org.openqa.selenium.support.ui.ExpectedConditions; import com.cucumber.automation.utils.AppiumFactory; public class AgendaPage extends AppiumFactory { // All the locators for Agenda page will be defined here By title = By.id("com.xebia.eventsapp:id/action_bar_custom_title"); By AgendaList = By.className("android.widget.LinearLayout"); By backButton = By.id("android:id/home"); // All the behavior of Agenda page will be defined here in functions public void verifyAgendaPage() { waitVar.until(ExpectedConditions.presenceOfElementLocated(title)); assertEquals("Agenda", driver.findElement(title).getText()); assertTrue(driver.findElements(AgendaList).size() >= 0); } public void clickBack() { driver.findElement(backButton).click(); } } -
我们将不得不更新我们刚刚编写的 Selenium 函数的步骤定义文件。添加所有函数后,代码应该如下所示:
package com.cucumber.automation.bdt.stepDefinitions; import com.cucumber.automation.mobile.pages.android.AgendaPage; import com.cucumber.automation.mobile.pages.android.HomePage; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; public class AgileNCRSD { HomePage hp = new HomePage(); AgendaPage ap = new AgendaPage(); @Given("^user is on AgileNCR Home Page$") public void user_is_on_AgileNCR_Home_Page() { hp.verifyHomePage(); } @Then("^user gets an option Agenda$") public void user_gets_an_option_Agenda() { hp.verifyHomePageOptions(); } @When("^user selects Agenda$") public void user_selects_Agenda() { hp.clickAgenda(); } @Then("^user is on Agenda Screen$") public void user_is_on_Agenda_Screen() { ap.verifyAgendaPage(); } @When("^user chooses to go back$") public void user_chooses_to_go_back() { ap.clickBack(); } } -
我们的框架已经准备好了。在我们开始执行之前,我们需要启动 Appium 服务器。使用默认设置启动 Appium 服务器,如下截图所示:
![如何操作…]()
-
我们可以从 Eclipse 或命令行运行此框架。让我们使用
mvn test从命令行运行它。以下是输出:![如何操作…]()
它是如何工作的…
我们已经集成了 Cucumber、Appium、Maven、Java 和页面对象来设计我们的移动自动化框架。Cucumber 用于实现 BDD,这样非技术人员也可以直接参与到开发中——Appium 用于网页自动化,Java 作为编程语言,Maven 作为构建工具。
Page Objects 是一种用于维护和访问分散在测试场景中的组件和控制的框架设计方法。Page Object 为我们的测试创建了一个 DSL;因此,如果页面上有东西发生变化,我们不需要更改测试;我们只需要更新代表页面的对象。
构建 REST 自动化框架
HTTP 客户端是由 Apache 开发的开源 REST 服务库。我使用 HTTP 客户端来构建这个框架,因为它是一个纯 Java 实现,非常易于使用。我们需要创建一个 HTTP 客户端实例,然后只需使用已定义的其中一个函数即可。
由于没有涉及 UI,并且我们对需要使用的功能有限制,因此在这个框架中不需要 POM。让我们学习如何使用 Cucumber 和 HTTP 客户端创建一个健壮的 REST 自动化框架。
注意
我只是演示 REST 服务自动化,同样的项目和框架也可以用于 SOAP 自动化。
如何操作…
让我们先了解在这个框架中我们将使用的测试用例。我将测试 GET 和 POST 方法。让我一步步说明测试步骤。
-
向 GitHub URL
api.github.com/users/ShankarGarg发送GET请求以验证用户详情。 -
向 Apple 服务中心
selfsolve.apple.com/wcResults.do发送POST请求以注册您的设备。 -
我们将使用 Eclipse Maven 插件创建一个简单的 Maven 项目。按照第一道菜谱中提到的步骤创建项目,并将其命名为
CucumberRESTAutomation。 -
由于这是一个 Maven 项目,我们将更新
pom.xml文件以添加 Cucumber 和 Appium 的依赖项。到目前为止,pom.xml文件看起来是这样的:<project 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>CucumberRESTAutomation</groupId> <artifactId>CucumberRESTAutomation</artifactId> <version>0.0.1-SNAPSHOT</version> <name>CucumberRESTAutomation</name> <description>Cucumber+HTTP Client project</description> </project> -
我们将添加
properties标签并定义 Cucumber 和 Appium 版本的属性,这样当我们需要更新依赖项版本时,我们只需在一个地方进行更新。将以下代码添加到pom.xml中:<properties> <http.version>4.4.1</http.version> <cucumber.version>1.2.2</cucumber.version> </properties>注意
要检查 Maven 中央仓库上的最新依赖项版本,请参阅
search.maven.org/。 -
我们将为 Cucumber-JVM 添加依赖项以支持 BDD,为 Appium-java 添加依赖项以支持移动自动化。将以下代码添加到
pom.xml中:<dependencies> <!-- cucumber --> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-java</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-junit</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency> <!-- HTTPClient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>${http.version}</version> </dependency> </dependencies> -
我们将创建项目结构,并将类似文件放在同一个包中。按照此截图中的步骤创建以下包:
![如何操作…]()
-
由于这是一个 Cucumber 项目,我们将向
CucumberOptions包添加RunCukeTest.java文件以指定配置,例如特征文件的位置、步骤定义的位置、输出位置等。将以下类添加到CucumberOptions包中:package com.cucumber.automation.bdt.cucumberOptions; import org.junit.runner.RunWith; import cucumber.api.CucumberOptions; import cucumber.api.junit.Cucumber; @RunWith(Cucumber.class) @CucumberOptions( features = "src/test/java/com/cucumber/automation/bdt/features", glue = "com.cucumber.automation.bdt.stepDefinitions", plugin = { "pretty", "html:target/cucumber", } ) public class RunCukeTest { } -
现在是时候添加特征文件以指定需求了。我们将添加
REST_Services.feature文件到features包中。这是我们的特征文件看起来是这样的:Feature: SOA Test In order to test rest services As a Registered user I want to specify the rest services test conditions Scenario: GET Request - GIT Hub User details When user sends a GET request with "https://api.github.com/users/ShankarGarg" Then status code should be 200 And response type should be "json" And response contains user name "Shankar Garg" Scenario: POST Request - Register a user When user sends a POST request to "https://selfsolve.apple.com/wcResults.do" with following details | key | value | | sn | C2WGC14ADRJ7 | | cn | | | locale | | | caller | | | num | 12345 | Then status code should be 200 And response type should be "html" And response contains user name "C2WGC14ADRJ7" -
下一步是添加步骤定义。最简单的方法是运行一次项目,并使用 Cucumber 提供的建议。在
stepDefinitions包中添加一个RESTServicesSD.java文件,并应包含以下代码:package com.cucumber.automation.bdt.stepDefinitions; import cucumber.api.DataTable; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; public class RestServicesSD { @When("^user sends a GET request with \"(.*?)\"$") public void user_sends_a_GET_request_with(String arg1) { } @Then("^status code should be (\\d+)$") public void status_code_should_be(int arg1) { } @Then("^response type should be \"(.*?)\"$") public void response_type_should_be(String arg1) { } @Then("^response contains user name \"(.*?)\"$") public void response_contains_user_name(String arg1) { } @When("^user sends a POST request to \"(.*?)\" with follwoing details$") public void user_sends_a_POST_request_to_with_follwoing_details( String arg1, DataTable arg2) { } } -
现在,我们需要添加 HTTP 客户端函数,这些函数可以用来发送
GET和POST方法。为了演示目的,我在文件中保留了所需的最小函数。package com.cucumber.automation.REST; import static org.junit.Assert.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.ParseException; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.ContentType; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import cucumber.api.DataTable; public class RESTFactory { @SuppressWarnings("deprecation") HttpClient client = new DefaultHttpClient(); static HttpResponse httpResponse = null; static String responseString = null; String getURL = ""; public void getRequest(String url) throws ClientProtocolException, IOException{ RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(20000).setConnectTimeout(20000).setSocketTimeout(20000).build(); HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig); getURL = url; HttpUriRequest request = new HttpGet( url ); httpResponse = builder.build().execute( request ); } public void verifyStatusCode(int statusCode) throws ClientProtocolException, IOException { assertEquals(statusCode, httpResponse.getStatusLine().getStatusCode()); } public void verifyResponseType(String type){ String mimeType = ContentType.getOrDefault(httpResponse.getEntity()).getMimeType(); assertTrue( mimeType.contains(type) ); } public void verifyResponseData(String responseData) throws ParseException, IOException{ HttpEntity entity = httpResponse.getEntity(); responseString = EntityUtils.toString(entity, "UTF-8"); assertTrue(responseString.contains(responseData)); } public void postRequest(String url, DataTable payloadTable) throws ClientProtocolException, IOException{ List<List<String>> payload = payloadTable.raw(); HttpPost post = new HttpPost(url); List<NameValuePair> urlParameters = new ArrayList<NameValuePair>(1); for (int i=1; i<payload.size();i++){ urlParameters.add(new BasicNameValuePair(payload.get(i).get(0), payload.get(i).get(1))); } post.setEntity(new UrlEncodedFormEntity(urlParameters)); httpResponse = client.execute(post); } }注意
预期读者了解关于 REST 服务的基础知识——例如
GET和POST方法等不同功能。 -
我们在这里不需要任何钩子文件,因为我们不需要在测试用例之前或之后调用任何浏览器/应用程序。如果需要,我们总是可以稍后添加该文件。
-
我们在这里不需要页面对象模型,因为没有需要维护的 UI 页面。
-
我们需要更新我们刚刚编写的 HTTP 函数的步骤定义文件。在添加所有函数后,代码应如下所示:
package com.cucumber.automation.bdt.stepDefinitions; import java.io.IOException; import org.apache.http.ParseException; import org.apache.http.client.ClientProtocolException; import com.cucumber.automation.REST.RESTFactory; import cucumber.api.DataTable; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; public class RestServicesSD { RESTFactory rt = new RESTFactory(); @When("^user sends a GET request with \"(.*?)\"$") public void user_sends_a_GET_request_with(String url) throws ClientProtocolException, IOException {rt.getRequest(url); } @Then("^status code should be (\\d+)$") public void status_code_should_be(int statuscode) throws ClientProtocolException, IOException { rt.verifyStatusCode(statuscode); } @Then("^response type should be \"(.*?)\"$") public void response_type_should_be(String type) { rt.verifyResponseType(type); } @Then("^response contains user name \"(.*?)\"$") public void response_contains_user_name(String userName) throws ParseException, IOException { rt.verifyResponseData(userName); } @When("^user sends a POST request to \"(.*?)\" with follwoing details$") public void user_sends_a_POST_request_to_with_follwoing_details(String url, DataTable payload) throws ClientProtocolException, IOException { rt.postRequest(url, payload); } } -
我们的框架已经准备就绪,我们可以从 Eclipse 或终端运行此框架。让我们使用
mvn test命令行来运行它。以下是将显示的输出:![如何操作…]()
它是如何工作的…
我们已经集成了 Cucumber、HTTP 客户端、Maven、Java 和页面对象来设计我们的 REST 服务自动化框架。Cucumber 用于实现行为驱动开发(BDD),以便非技术人员也可以直接参与开发——HTTP 客户端用于 REST 自动化,Java 作为编程语言,Maven 作为构建工具。






























































浙公网安备 33010602011771号