使用Cucumber × Puppeteer × chai 搭建E2E测试执行环境在BDD开发中
我进行了E2E测试环境的搭建和样例实现。
E2E测试是端到端测试。
为了测试用户在使用服务时的视角,将系统运行起来,以接近实际运行时的形式。这次我们假设是在Web上进行测试,并搭建了一个能在浏览器中运行的端对端测试环境。
E2E测试(集成测试)的优点与缺点的参考网址。
※这次考试的一部分将使用以下网页:
使用工具
小黄瓜.js
Cucumber-JS是一个处理BDD(行为驱动开发)框架的JavaScript库。通过在称为feature的文件中编写用户行为并基于这些行为来执行系统操作。
使用Gherkin语言来描述这些行为。
GitHub链接:https://github.com/cucumber/cucumber-js
如果对BDD不太了解的话,下面的文章可能会有所帮助:
参考链接:https://www.atmarkit.co.jp/ait/articles/1403/25/news033.html
木偶师 (Mù Ǒu Shī)
Puppeteer是一个用于操作Chrome和无头Chrome的库。
与Selenium类似,但是Puppeteer不需要单独安装Web Driver。
GitHub:https://github.com/puppeteer/puppeteer
拆解 jiě)
这是一个用于测试断言的库。在这次使用中,它将用于检查特定HTML标签内的字符串是否符合预期。GitHub链接:https://github.com/chaijs/chai
环境搭建步骤
切换 Node.js 版本
我使用了Nodebrew,将版本升级至v13.9.0。
$ nodebrew install v13.9.0
$ nodebrew use v13.9.0
创建package.json
这次使用yarn进行软件包安装。
$ yarn init
安装各种软件包
我要安装puppeteer、cucumber和chai。
$ yarn add puppeteer --dev
$ yarn add cucumber --dev
$ yarn add chai --dev
添加执行命令的别名
在package.json中添加用于从cucumber运行测试的命令。
{
"name": "e2e_test",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"e2e_test": "./node_modules/.bin/cucumber-js" // 追加
},
"devDependencies": {
"chai": "^4.2.0",
"cucumber": "^6.0.5",
"puppeteer": "^2.1.1"
}
}
启动用于E2E测试的服务器。
此次我们将以nginx服务器为例进行部署。
显示默认页面,并执行使用该页面及其后续页面进行的端到端测试。
从另一个终端启动服务器的Docker容器。
$ docker run -p 8080:80 nginx
图像呈现
现在我们来编写一个测试用例,其中将用户行为设定为到目前为止的动作。下面是设定的场景:
-
- シナリオ名: nginxの初期表示画面から公式ページに飛ぶことができる
-
- 前提条件: nginxの初期表示画面が表示されている
-
- アクション: nginx.com のリンクをクリックする
- 結果: 遷移したページに Welcome to NGINX! が表示されている。
实施
特征文件
首先,创建一个名为 features 的目录,并创建一个名为 nginx.feature 的文件。
在这个文件中,编写用户要遵循的场景。
Feature: nginx画面
エンドユーザーがnginxの様々なページで動作を行うシナリオ
Scenario: nginxの初期画面から公式ページに飛ぶことができる
Given nginxの初期画面が表示されている
When "nginx.com" のリンクをクリックする
Then 遷移したページに "Welcome to NGINX!" が表示されている
在执行cucumber.js时,将自动加载features目录,并自动执行其中的〇〇.feature文件。
当以当前状态执行时,会在控制台上显示“没有为场景定义的实现部分”这样的警告信息。
$ ./node_modules/.bin/cucumber-js
UUU
Warnings:
1) Scenario: nginxの初期画面から公式ページに飛ぶことができる # features/nginx.feature:4
? Given nginxの初期画面が表示されている
Undefined. Implement with the following snippet:
Given('nginxの初期画面が表示されている', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
? When "nginx.com" のリンクをクリックする
Undefined. Implement with the following snippet:
When('{string} のリンクをクリックする', function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
? Then 遷移したページに "Welcome to NGINX!" が表示されている
Undefined. Implement with the following snippet:
Then('遷移したページに {string} が表示されている', function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
1 scenario (1 undefined)
3 steps (3 undefined)
0m00.000s
error Command failed with exit code 1.
只需将显示的片段复制到用于实施操作的文件中即可。
实施行动
创建一个名为”step_definitions”的目录,并在其中创建一个名为”nginx_steps.js”的文件。
将先前显示的代码段粘贴到该文件中。
不要忘记添加”Given”等方法的导入。
const { Given, When, Then } = require("cucumber");
Given('nginxの初期画面が表示されている', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
When('{string} のリンクをクリックする', function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
Then('遷移したページに {string} が表示されている', function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
这个就可以了。
当执行时,与之前不同的是,一旦给出结果为”Pending”,测试就会停止。
$ yarn run e2e_test
yarn run v1.22.0
$ ./node_modules/.bin/cucumber-js
P--
Warnings:
1) Scenario: nginxの初期画面から公式ページに飛ぶことができる # features/nginx.feature:4
? Given nginxの初期画面が表示されている # features/step_definitions/nginx_steps.js:3
Pending
- When "nginx.com" のリンクをクリックする # features/step_definitions/nginx_steps.js:8
- Then 遷移したページに "Welcome to NGINX!" が表示されている # features/step_definitions/nginx_steps.js:13
1 scenario (1 pending)
3 steps (1 pending, 2 skipped)
0m00.000s
error Command failed with exit code 1.
顺便提一下,在cucumber的场景中,用双引号括起来的部分表示导航到的页面上显示了”Welcome to NGINX!”。
因此,在执行场景时,这部分将作为参数传递给方法。
因此,在step_definitions/nginx_steps.js中,When和Then方法都有相应的部分作为{string}参数进行定义。
操纵木偶的实现方法
鉴于
首先,这是nginx的初始页面。
我先用puppeteer编写了一个展示页面的部分。
const { Given, When, Then } = require("cucumber");
const puppeteer = require('puppeteer');
var page = null;
Given('nginxの初期画面が表示されている', async function () { // awaitを使うのでasyncを追加。
const browser = await puppeteer.launch({headless: false}); // ブラウザを表示するのでheadlessをfalseに
page = await browser.newPage();
await page.goto('http://localhost:8080'); // http://localhost:8080に遷移
});
// 以下省略
然而,在这种情况下,无法保证“显示的是nginx的初始页面”在给定中。因此,对于显示在屏幕上的元素进行断言。
const { Given, When, Then } = require("cucumber");
const puppeteer = require('puppeteer');
const { assert } = require('chai');
var page = null;
Given('nginxの初期画面が表示されている', async function () {
const browser = await puppeteer.launch({headless: false});
page = await browser.newPage();
await page.goto('http://localhost:8080');
// h1タグ内の文字列を取得
const h1Text = await page.$eval('h1', item => {
return item.textContent;
});
assert.equal(h1Text, 'Welcome to nginx!'); // h1タグ内の文字列が「Welcome to nginx!」であるかのアサーション
});
// 以下省略
现在可以实现对Given内的检查。当执行时,Given的测试通过,而When则变为待定。
$ yarn run e2e_test
yarn run v1.22.0
$ ./node_modules/.bin/cucumber-js
.P-
Warnings:
1) Scenario: nginxの初期画面から公式ページに飛ぶことができる # features/nginx.feature:4
✔ Given nginxの初期画面が表示されている # features/step_definitions/nginx_steps.js:5
? When "nginx.com" のリンクをクリックする # features/step_definitions/nginx_steps.js:18
Pending
- Then 遷移したページに "Welcome to NGINX!" が表示されている # features/step_definitions/nginx_steps.js:23
1 scenario (1 pending)
3 steps (1 pending, 1 skipped, 1 passed)
0m01.351s
it rains, the grass grows.
点击「nginx.com」。
// Givenは省略
When('{string} のリンクをクリックする', async function (linkText) {
const linkXpath = `//a[text() = "${linkText}"]`; // nginx.comリンクのxpathを取得
await page.waitForXPath(linkXpath);
await (await page.$x(linkXpath))[0].click(); // リンクをクリック
});
// 以下両略
还有,我们已经根据以下参考实施了使用 Puppeteer 的 innerText 方法来选择具有指定文本的链接的点击方式。
然后
最后了。
我们可以通过点击(click())方法来确保链接跳转到官方网页,然后检查跳转页面是否显示了“Welcome to NGINX!”。
顺便一提,初始页面的nginx是小写字母,而官方页面的NGINX是大写字母。
// 省略
Then('遷移したページに {string} が表示されている', async function (pageTitleText) {
await page.waitForNavigation({ waitUntil: "domcontentloaded" }); // ページのローディングが終わるまで待つ
// h1タグ内の文字列を取得
const h1Text = await page.$eval('h1', item => {
return item.textContent;
});
assert.equal(h1Text, pageTitleText); // h1タグ内の文字列が「Welcome to NGINX!」であるかのアサーション
});
在这个状态下执行,执行所有条件和断言,直到完成所有测试,输出测试完成。
$ yarn run e2e_test
yarn run v1.22.0
$ ./node_modules/.bin/cucumber-js
...
1 scenario (1 passed)
3 steps (3 passed)
0m04.815s
以上是实施和确认的结束!
已完成的文件 (Yǐ de
特点/ Nginx 特点特性.
Feature: nginx画面
エンドユーザーがnginxの様々なページで動作を行うシナリオ
Scenario: nginxの初期画面から公式ページに飛ぶことができる
Given nginxの初期画面が表示されている
When "nginx.com" のリンクをクリックする
Then 遷移したページに "Welcome to NGINX!" が表示されている
step_definitions/nginx_steps.js 测试步骤定义/nginx_steps.js
const { Given, When, Then } = require("cucumber");
const puppeteer = require('puppeteer');
const { assert } = require('chai');
var page = null;
Given('nginxの初期表示画面が表示されている', async function () {
const browser = await puppeteer.launch({headless: false});
page = await browser.newPage();
await page.goto('http://localhost:8080');
// h1タグ内の文字列を取得
const h1Text = await page.$eval('h1', item => {
return item.textContent;
});
assert.equal(h1Text, 'Welcome to nginx!'); // h1タグ内の文字列が「Welcome to nginx!」であるかのアサーション
});
When('{string} のリンクをクリックする', async function (linkText) {
const linkXpath = `//a[text() = "${linkText}"]`; // nginx.comリンクのxpathを取得
await page.waitForXPath(linkXpath);
await (await page.$x(linkXpath))[0].click(); // リンクをクリック
});
Then('遷移したページに {string} が表示されている', async function (pageTitleText) {
await page.waitForNavigation({ waitUntil: "domcontentloaded" }); // ページのローディングが終わるまで待つ
// h1タグ内の文字列を取得
const h1Text = await page.$eval('h1', item => {
return item.textContent;
});
assert.equal(h1Text, pageTitleText); // h1タグ内の文字列が「Welcome to NGINX!」であるかのアサーション
});
最后
使用BDD框架可以自动输出方法来处理行为,以简明的方式实现而不需要花费太多工时。此外,使用BDD框架编写测试的话,将成为代表功能的文档,描述用户服务的特性文件,这在团队开发中有助于更容易认识产品核心功能。
请以此为参考。