如果您正在寻找一个简单的测试框架做自动化测试,您可以考虑用于使用行为驱动框架 (BDD),具体技术使用流行的 JavaScript,Selenium 和 Cucumber 的组合。它们作为一些流行的自动化技术,可以帮你完成 Web 应用自动化测试的任务。下面将详细介绍如何使用 JavaScript,Selenium 和 Cucumber 实现自动化测试套件。

当试图在项目中利用 BDD 构建自动化测试套件时,有人会对如何具体实现陷入迷茫,就好像这是一项艰巨的任务 -  事实并非如此。

希望本文能够在一定程度上揭开这个过程的神秘面纱,并提供一些有用的技巧来帮助您开始您的项目。

什么是 BDD?

BDD(行为驱动开发)是一组开发实践,旨在减少由常见的开发问题引起的错误 - 例如由于需求被误解或需求阐述不清而不得不重新开始工作。

BDD 是 TDD 的扩展, 主要差异在于 BDD 指定了测试用例。

BDD 使用清晰,通俗易懂的自然语言来测试案例,这些测试案例来自 “从外到内”  - 即直接来自业务需求和应用程序行为的期望业务成果。

这些测试用例中使用的明确语言通常以 Gherkin 这种域特定语言形式编写。

Gherkin 样例

每一个 web 应用几乎都需要有注册,登陆功能,我们以基本登陆用例为主。首先如下是 Gherkin 中的描述。

# language: zh-CN
功能: Web应用程序的登录页面

  背景:用户未登陆
    假如我浏览到Web应用程序而我尚未登录
  场景:显示的登录页面
    假如浏览到网站首页
    当点击登录按钮应该跳转到登录页面

  场景:我尝试使用无效用户登录
    当我使用无效用户登录
    同时我看到"用户名或密码不正确"错误消息

  场景:我尝试使用有效用户登录
    当我使用有效用户登录
    同时我看到一个"首页"页面

以这种形式编写测试用例的真正好处有两个:

步骤定义

步骤定义 (Step Definition) 将 Gherkin 中 feature 文件定义的步骤 (step) 映射到具体操作代码。我们已经在 feature 文件中记录了我们希望应用程序期望的行为,现在我们需要编写代码测试该行为。

使用 Cucumber,我们可以用以下形式编写空白步骤定义 - 其中注释和正则表达式将特征文件中的步骤映射到具体操作:


var { Given, When, Then } = require('cucumber')

Given(/^我浏览到Web应用程序而我尚未登录$/, async function () {
    return 'pending';
});

Given(/^浏览到网站首页$/, async function () {
    return 'pending';
});

When(/^点击登录按钮应该跳转到登录页面$/, async function () {
    return 'pending';
});

When(/^我使用无效用户登录$/, async function () {
    return 'pending';
});

When(/^我看到"([^"]*)"错误消息$/, async function (arg1) {
    return 'pending';
});

When(/^我使用有效用户登录$/, async function () {
    return 'pending';
});

When(/^我看到一个"([^"]*)"页面$/, async function (arg1) {
    return 'pending';
});

用于浏览器自动化的 Selenium

我们希望能够运行我们的脚本测试实际运行的 Web 应用程序。为此,我们可以在 Cucumber 步骤定义中编写 Selenium 自动化库的代码。

Selenium 是一个浏览器自动化框架,您可以利用它的 WebDriver 对象操作真实浏览器和网页,并使用 CSS 或 XPath 选择器在屏幕上定位元素。WebDriver 针对各种不同的浏览器有不同的实现,它还能运行没有 GUI 的浏览器,它也叫无头 (headless) 浏览器。

下面的 web_driver.js 文件中实现的代码创建了 WebDriver 的实例,以供后继调用。

// 引入 Chrome驱动
require('chromedriver');
// 引入Selenium-Webdriver
const webDriver = require('selenium-webdriver');

//根据您的浏览器配置创建WebDriver实例;
function createDriver() {
    let browserConfig = process.env.BROWSER || 'chrome';
    let browser = browserConfig.toLowerCase();
    if (['chrome', 'firefox', 'ie'].indexOf(browser) < 0) browser = 'chrome'; //default to chrome
    return new webDriver.Builder().forBrowser(browser).build();
}

exports.driver = createDriver();

页面对象模型

页面对象模型 (Page Object,或 PO) 是一种设计模式,它将页面上的页面和元素表示为面向对象的类。当您需要与屏幕上的元素交互时,您不直接调用 WebDriver。而是在类上调用方法表示页面上的元素。

让我们以登录页面应用为例,编写如下的 login_page.js 文件:

let {driver} = require('./support/web_driver')

class LoginPage {

    constructor(){
        this.Base_Url = "http://localhost";
        this.Login_Css_selector='#login';
        this.UserName_Css_selector="#name";
        this.PassWord_Css_selector="#pass";
        this.Login_Btn_Css_selector="#login";
        this.error_msg_Css_selector=".error";
        this.index_Csss_selector='.index';
    }

    async go_Home() {
        await driver.get(this.Base_Url)
    }

    async go_LoginPage(){
        await driver.findElement({css:this.Login_Css_selector}).click();
    }

    async set_UserName(username){
        await driver.findElement({css:this.UserName_Css_selector}).sendKeys(username);
    }

    async set_PassWord(pass){
        await driver.findElement({css:this.PassWord_Css_selector}).sendKeys(pass)
    }

    async click_Login_btn(){
        await driver.findElement({css:this.Login_Btn_Css_selector}).click();
    }

    async action_login(username,passwd){
        await this.set_UserName(username);
        await this.set_PassWord(passwd);
        await this.click_Login_btn();
    }
}

module.exports = new LoginPage();

上面的类表示我们的登录页面 - 它定义了页面上的主要元素,并且实现了常用的操作。

实现步骤定义

根据上面定义的 PO 模型,在步骤定义 (definition.js) 中实现 feature 的代码:


var { Given, When, Then } = require('cucumber')
const assert = require('assert');
const LoginPage = require('./login_page')
const {driver} = require('./support/web_driver')

Given(/^我浏览到Web应用程序而我尚未登录$/, async function () {
    await LoginPage.go_Home()
});

Given(/^浏览到网站首页$/, async function () {
     await LoginPage.go_Home()
});

When(/^点击登录按钮应该跳转到登录页面$/, async function () {
    await LoginPage.click_Login_btn()
    let page_url = await driver.getCurrentUrl()
    assert.equal(page_url,'http://localhost/login')
});

When(/^我使用无效用户登录$/, async function () {
    await LoginPage.action_login('无效用户名','无效密码');

});

When(/^我看到"([^"]*)"错误消息$/, async function (msg) {
    let text = await driver.findElement({css:LoginPage.error_msg_Css_selector}).getText();
    assert.equal(text,msg)
});

When(/^我使用有效用户登录$/, async function () {
    await LoginPage.action_login('user','pass')
});

When(/^我看到一个"([^"]*)"页面$/, async function (arg1) {
    let text = await driver.findElement({ css: LoginPage.index_Css_selector }).getText();
    assert.equal(text, msg)
});

更多

在构建应用程序并添加更多功能时,会通过更多测试。为了确保测试通过,您只需要使用适当的选择器(在页面对象模型和元素中定义)选择元素,实现操作。

选用合适的测试工具会让你代码生成和调试事半功倍,例如在 CukeTest 中,可视化的显示了场景和相关步骤定义的关系并可即时跳转:

此外点击运行,也可快速得到报表:


↙↙↙阅读原文可查看相关链接,并与作者交流