E2E 测试与自动化测试工具 Nightmare

1. 前端的自动化测试

1.2 单元测试

单元测试往往只关注于一个代码片段,通常是一个模块或函数。实际应用中会将代码拆分成若干个小的组件,这也意味着你会写很多的单元测试用例来保证代码的功能正常。

前端开发中的单元测试工具有 jestmochajasminequnit

1.3. E2E 测试

E2E(end to end)测试即端到端测试是,也称冒烟测试,用于测试真实浏览器环境下前端应用的流程和表现,相当于代替人工去操作应用。E2E 测试是一个边界比较模糊的概念,有以下几个特点:

  • 把整个系统当作一个黑盒
  • 测试人员模拟真实用户在浏览器中操作UI
  • 测试出的问题可能是前端也可能是后端导致的

常见的 E2E 测试工具有 nightmare、nightwatch 和 puppeteer

1.4. 单元测试和 E2E 测试的对比

单元测试的概念出现已久,相关的工具体系也已经十分完善,但是单元测试的维护成本较高,并且对于前端测试来说有很多需求无法满足。此外,单元测试需要较多的角度完善的测试用例支持,且这些测试用例都是较为简单的,针对每一个小模块或者组建的输入与输出。

E2E 测试针对具体的测试环境条件来编写测试用例,一般情况下测试用例不会太多.

1.5. TDD 和 BDD

TDD(Test Drive Development)即测试驱动开发。简单的说就是先根据需求写测试用例,再代码实现,接着测试,循环此过程直到产品的实现。可以看出来,TDD 的基本思路就是通过测试来推动整个开发的进行,但测试驱动开发并不只是单纯的测试工作,而是把需求分析,设计,质量控制量化的过程。

BDD(Behavior Drive Development)即行为驱动开发,BDD 可以看作是对 TDD 的一种补充,或者说是 TDD 的一个分支。在TDD中,我们并不能完全保证根据设计所编写的测试就是用户所期望的功能。BDD将这一部分简单和自然化,用自然语言来描述,让开发、测试、BA以及客户都能在这个基础上达成一致。BDD 更加依赖于需求行为和文档来驱动开发,这些文档的描述跟测试代码很相似。

通过对比,TDD 通过测试来推动整个开发的进行。这有助于编写简洁可用和高质量的代码,并加速实际开发过程,可能导致的问题是需求和开发脱节,实际产品与用户所需要的功能并不匹配。而 BDD 通过鼓励项目中的开发者、QA 和非技术人员或商业参与者之间进行协作,确保程序实现效果与用户需求一致。

可以看出 E2E 测试更多的是换和 BDD 的开发模式进行结合,实际应用用会将 E2E 的测试工具和 BDD 测试框架进行结合。

2. Nightmare

2.1.基本介绍

Nightmare 是一个基于 electron 的浏览器自动化代码库,用于实现爬虫或自动化测试。相较于传统的爬虫框架(scrapy/pyspider),或者dom操作库(cheerio/jsdom),或者基于浏览器的自动化框架(selenium/phantomjs),他的优势在于提供了一个简洁有效的编程模型。

官网给出的实现一个向yahoo自动提交关键词并搜索的功能如下:

1
2
3
4
yield Nightmare()
.goto('http://yahoo.com')
.type('input[title="Search"]', 'github nightmare')
.click('.searchsubmit');

2.2. 相关 API

基本的交互 API 包括:

  • goto(url[, headers]) 跳转到url
  • viewport(width, height) 浏览器窗口大小
  • wait(selector) 等待某个dom元素出现
  • click(selector) 点击某个dom元素
  • type(selector[, text]) 在某个dom元素中输入
  • inject(type, file) 在页面上挂载 js/css 文件内容
  • evaluate(fn[, arg1, arg2,...]) 在客户端注入JS脚本并执行,从而实现electron模型下的C/S互动及数据交换
  • ……

2.3. 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Nightmare from 'nightmare';

describe('Login', () => {
let page;
beforeEach(() => {
page = Nightmare();
page.goto('http://localhost:8000/#/user/login');
});

it('should login with failure', async () => {
await page.type('#userName', 'mockuser')
.type('#password', 'wrong_password')
.click('button[type="submit"]')
.wait('.ant-alert-error') // should display error
.end();
});

it('should login successfully', async () => {
const text = await page.type('#userName', 'admin')
.type('#password', '888888')
.click('button[type="submit"]')
.wait('.ant-layout-sider h1') // should display error
.evaluate(() => document.body.innerHTML)
.end();
expect(text).toContain('<h1>Ant Design Pro</h1>');
});
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const expect = require('chai').expect;
const Nightmare = require('nightmare');
const assert = require('assert');

describe('simulate search', function () {
  this.timeout('30s')

  let nightmare = null
  beforeEach(() => {
    // 设置显示模拟弹框
    nightmare = new Nightmare({
    show: true,
   })
  })

  it('模拟用户搜索', done => {
    nightmare
    .goto('https://www.baidu.com/') //设置搜索引擎
    .viewport(1200, 672)   //设置弹框视口宽高        
    .type('form[action*="/s"] [name=wd]', 'github nightmare') //获取搜索框,自动填充搜索内容
    .click('form[action*="/s"] [type=submit]') //获取点击按钮,模拟点击
    .wait(5000) //等待5s(可为dom节点),获取第一条的信息的内容
    .evaluate(() =>
     document.querySelector('#content_left .c-container a em').innerHTML
    )
    .end()
    .then(content => {
     console.log(content === 'Nightmare','----->true') //输出结果
done();
    })
    .catch(error => {
     console.error('Search failed:', error); //输出捕捉到的错误
done();
    });
  })
})

参考: