编写端到端测试

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-02-26

了解端到端测试

端到端测试通过自动运行浏览器与正在运行的应用程序交互来检查应用程序的行为是否正确,代码单元是否协同工作

手动测试应用程序时,需要打开应用程序,进行一些单机操作,确保应用程序正确响应。端到端测试除了不是人与应用程序交互而是程序与程序交互之外,其他都是一样的。

假设你点击新链接时,应用程序会渲染一个新列表。如果是手动测试,你将运行应用程序服务器,在浏览器打开应用程序,单击新链接,检查路由是否更改,并检查内容是否更新。端到端测试会使浏览器自动执行这些完全相同的操作。

端到端测试也有一些缺点:它们速度慢,很难调试,而且可能会很脆弱。节制使用是高效使用端到端测试的关键。不要将所有测试都写成端到端测试,而应该只写一些贯穿核心使用者操作交互的测试。将端到端测试视为单元测试和快照测试的补充,单元测试和快照测试会全面检查应用程序的组件和功能,而端到端测试会检查这些组件和功能是否工作正常。

了解 Nightwatch 和 WebDriver

Nightwatch 是一个用于自动运行浏览器的 JavaScript 框架,在底层使用 WebDriver 来控制浏览器,WebDriver 是一个自动运行浏览器的接口。

使用 WebDriver 最流行的方法是使用 Seleniunm Server ———— 一个带有 REST API(用于应用 WebDevices 协议) 的 Java servlet。编写直接与 Seleniunm 通信的测试是很困难的,你需要管理浏览器会话,并且请求的 URL 会使得测试难以读取(例: http://localhost:4444/wd/hub/session/1352110219202/element/0/click),Nightwatch 提供了一个对 WebDriver API 的实现。

Nightwatch工作流程

Nightwatch 使用 HTTP 请求与 Seleniunm Server 进行交互,然后 Seleniunm Server 将命令转发到浏览器以执行。

把 Nightwatch 添加到项目中

1、安装依赖

npm i --save-dev nightwatch seleniunm-server

# 安装浏览器特定的驱动程序:指的是可以被 Seleniunm Server 使用以便在不同浏览器执行测试的程序
# 例如安装对应谷歌浏览器的驱动程序
npm i --save-dev chromedriver

2、配置 Nightwatch

对应的 nightwatch.conf.js

module.exports = {
  // 放置测试文件的目录
  src_folders: ['e2e/specs'], 
  // 输出测试报告的内容
  output_folder: 'e2e/reports', 

  selenium: {
    start_process: true,
    // Seleniunm Server 二进制文件路径,该路径会被 npm 包输出出来
    server_path: require('selenium-server').path, 
    host: '127.0.0.1',
    // Seleniunm Server 的默认端口
    port: 4444, 
    cli_args: {
      // 设置Nightwatch以使用chromedriver路径启动Seleniunm进程
      'WebDriver.chrome.driver': require('chromedriver').path,
    }
  },

  test_settings: {
    chrome: {  // chrome 测试环境的设置 测试环境是通过 --env xx 传递给 nightwatch 的
      desiredCapabilities: {
        browserName: 'chrome'
      }
    },
  }
}

3、增加 package.json 中的脚本

// pakeage.json

{
  "script": {
    // ...
    "test:e2e": "nightwatch --config e2e/nightwatch.conf.js --env chrome"
  }
}

4、添加一个可用性的测试

// e2e/specs/journeys.js
module.exports = {
  'sanity test': function (browser) {
    browser
      .url('http://localhost:8080')
      .waitForElementVisible('.items-list', 2000)
      .end()
  },
}

测试被定义为对象上的方法,并使用属性名作为规范名。

5、编写端到端测试脚本

需要注意的是,要在本地对应用程序运行 Nightwatch 测试,应用程序必须处于正在运行的状态(要启动本地服务)。这里有个问题就是,为了分别运行本地服务和测试,你必须在两个单独的终端选项卡中运行这两个进程,很明显这很不方便,本地开发加跑测试用例还好说,当要集成部署的时候就比较麻烦了,所以解决方案是写一个脚本来帮你启动服务器,然后在单独的进程中运行 Nightwatch。

这里的情况增加 package.json 的 script 字段是不行的,例如"test:e2e": "npm run start && nightwatch --config e2e/nightwatch.conf.js --env chrome",这样代表它们会执行在一个终端中,且当服务跑起来之后串行执行的部分 nightwatch --config e2e/nightwatch.conf.js --env chrome 就没法执行了。

&& 串行执行脚本
& 并行执行脚本

// e2e/runner.js
const app = require('../server'); // 启动本地服务的服务端代码 node express
const spawn = require('cross-spawn');

const PORT = process.env.PORT || 8080;

// 在对应端口启动服务监听
const server = app.listen(PORT, () => { 
  const opts = ['--config', 'e2e/nightwatch.conf.js', '--env', 'chrome'];
  // 生成一个子进程,该子进程运行 nightwatch 二进制文件,并将对应参数 opts 传入
  // 等同于执行 nightwatch --config e2e/nightwatch.conf.js --env chrome 命令
  // 它会生成一个运行该命令的子进程,stdio: 'inherit' 选项告诉子进程将所有内容打印到子进程
  // 这样在运行脚本的时候,你将在终端中看见输出。
  const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' });

  runner.on('exit', function (code) { 
    server.close()
    process.exit(code)
  })

  runner.on('error', function (err) { 
    server.close()
    throw err
  })
})

使用 Nightwatch 编写端到端测试

1、选择要编写哪些端到端测试

决定哪些测试要写哪些不写是一门艺术,编写尽可能少的端到端测试很重要,因为端到端测试脚本很快就会变得缓慢和不稳定,要有选择地编写只检查核心用户操作的测试。

编写高效的端到端测试的关键是使用它们来执行主要操作,一个常见错误是用它来检查页面的 HTML,这简直就是单元测试和快照测试的代价高昂的重复操作。当然有时你需要检查 HTML,但也请将其保持在最低限度内使用。

2、为路由编写端到端测试

路由很难用单元测试进行测试,这使得它成为编写端到端测试的良好候选,端到端测试的难点之一是决定应该断言什么来告诉你一个交互处于正确运行状态。比如手动测试时,你需要进行单击跳转页面的测试,此时你打开浏览器单击链接,可以很直观的看到页面是否正确,但是这里的问题是如何告诉程序页面是正确的?

对于这一点,你可以检查是否渲染了一个对新页面而言是唯一的元素、检查 URL 是否更新。

module.exports = {
  // 检查链接是否导航正确
  'takes user to the item page': function (browser) {
    browser
      .url('http://localhost:8080')
      .waitForElementVisible('.news-item', 15000)
      .click('.comments-link')
      .assert.urlContains(`/item`)
      .waitForElementVisible('.item-view', 15000)
      .end()
  },
  // 检查路由是否更新
  'clicking on a user redirects to  the user page': function (browser) {
    browser
      .url('http://localhost:8080')
      .waitForElementVisible('.news-item', 15000)
      .click('.by a')
      .assert.urlContains(`/user`)
      .waitForElementVisible('.user-view', 30000)
      .end()
  },
}

这里 waitForElementVisible 的时间设置是 15000(15秒),如果你的网络连接很糟糕的话,你可以增加等待的时间,相反,你可以等待的时间设置的短一点。

flaky test

端对端测试饱受被称为 flaky test 的问题的困难,flaky test 指的是那些即便代码工作正常,但却时有失败的测试。测试不稳定的原因有很多种。例如 API 调用的响应耗时过久,测试会超时并失败。

当你的测试套件中有一个 flaky test 时,你会开始忽略失败的测试,很容易陷入一种习惯,认为失败的测试套件只是另一个不稳定的测试。这将使得你的测试套件变得低效。在一个高效的测试套件中,任何失败的测试都会告诉你应用程序里有一个 bug。

可以在端到端测试中添加长超时(long timeout) 来避免 flaky test。如果一个 API 调用比预期花费更多的时间,测试也不会失败。很难完全避免 flaky test,最好的方法是尽可能少的编写端到端测试,同时仍然可以测试核心用户操作流程。

3、为动态数据编写端到端测试

检查使用了动态数据的应用程序是很困难的,例如那些每日实时资讯的页面随着时间的流动显示不同的内容,你不能把一个值硬编码到测试中。

比如,你需要测试分页是否正常工作,你可以通过单击分页链接时断言 URL 更新来完成此操作,但这并不是一个严格的测试,它不会验证页面内容是否已更改。

测试页面内容是否已更改的一种方法是将以前的页面内容保存为变量,并将其与新的页面内容进行比较。但这种测试仍然不是完美的(新的内容是否满足预期),但是它确实可以告诉你内容已经改变了。

举例:编写一个测试,它将会把列表文本以变量的形式保存起来,然后使用页面链接进行导航,断言当前文本已经从上一个完成了更改,这样你就知道列表值已经更新了。

module.exports = {
  'paginates items correctly': function (browser) {
    let originalItemListText;

    browser
      .url('http://localhost:8080')
      .waitForElementVisible('.news-item', 15000) // 等待列表加载完成
      .getText('.item-list', function (result) {
        // 获取当前列表文本并保存到变量originalItemListText中
        originalItemListText = result.value.slice(0, 100) 
      })
      .click('.item-list-nav a:nth-of-type(2 )') // 点击下一页按钮
      .waitForElementNotPresent('.progress', 15000) // 等待进度条消失 即加载完成
      .perform(() => {
        // perform 提供一个回调来执行命令 
        // 这里指当数据异步加载完之后调用回调执行断言 第二页的文本已经更新与第一页的不一样了 
        browser.expect.element('.item-list').text.to.not.equal(originalItemListText)
      })
      .getText('.item-list', function (result) {
        // 存储第二页的文本
        originalItemListText = result.value.slice(0, 100)
      })
      .click('.item-list-nav a') // 点击上一页回到第一页
      .waitForElementNotPresent('.progress', 15000)
      .perform(() => {
        // 断言第二页的文本与第一页的文本不一样
        browser.expect.element('.item-list').text.to.not.equal(originalItemListText)
      })
  },
}

4、在多浏览器运行端到端测试

使用 Nightwatch 编写端到端测试的一个好处是:使用极少的配置就可以在多个浏览器中运行,例如,如果你想在 Firefox 中运行测试。

第一步:安装 Firefox 驱动程序 geckodriver

npm i --save-dev geckodriver

第二步:更新 Nightwatch 配置

修改上面的 nightwatch.conf.js:

module.exports = {
  // ...
  selenium: {
    // ...
    cli_args: {
      'WebDriver.chrome.driver': require('chromedriver').path,
      'WebDriver.gecko.driver': require('geckodriver').path
    }
  },
  test_settings: {
    chrome: { 
      desiredCapabilities: {
        browserName: 'chrome'
      }
    },
    firefox: {
      desiredCapabilities: {
        browserName: 'firefox' 
      }
    }
  }
}

第三步:更新 nightwatch 运行参数

// e2e/runner.js
const opts = ['--config', 'e2e/nightwatch.conf.js', '--env', 'chrome,firefox'];