前端的测试工具

背景

在看Angular的测试时,发现了许多新的术语 jasmine, karma, e2e, spy 等等,
认为非常有必要先了解一下前端测试的概念和工具集.

测试原因

近年来出现了许多前端框架,SPA渐渐兴盛,
许多原本由后端进行的工作被带到了前端进行.
相应功能和函数的数量越来越多,复杂性越来越高.
自然地,对一些功能的测试就必不可少.

测试分类

前端测试也分许多种类

  • 单元测试
  • 集成测试
  • UI测试
  • 端到端测试

其中单元测试的代码通常和功能代码放在相同路径下(至少angular是这样的),
这样做有许多好处

  1. 易于找到,且能发现哪些功能还没有测试
  2. 移动或重命名时不会忘记测试代码

端到端(e2e)测试是站在用户角度的测试,属于黑盒测试.
如果有这么一个工具进行e2e测试,那么它至少需要能够模拟出浏览器中的操作.
而测试用的代码,由于通常是跨文件跨功能的测试,
因此不选择和特定功能放在一起,而是单独放一个文件夹,比如 e2e

测试框架

前端框架通常需要提供的功能有

  1. 断言功能:对比函数的实际输出和期望输出(核心功能)
  2. 对测试用例的结构进行管理(比如建立测试集的概念,方便代码的组织)
  3. 管理测试用代码在运行时的一些工作
    1. 公共变量的赋值
    2. 依赖注入
    3. 仿真(模拟一个返回值,方法,模块,甚至服务器)
  4. 生成,展示测试结果
  5. 调用浏览器或模拟浏览器等等

事实上直接沿用许多后端测试框架的概念就可以了,顶多多加一个浏览器的支持

前端框架也有不少

  • Jasmine Angular建议的一个测试框架,辈分奇高.
  • Mocha 曾经用的人多.
  • Jest 受到Jasmine启发而开发的框架,由Facebook团队开发和维护,目前最受欢迎.
  • Tape 体积小,只提供最关键的东西

jasmine

一个前端测试框架,特点有

  • 不依赖其他js框架
  • 不使用DOM(TODO意味着什么?)
  • 开箱即用(自带断言和仿真功能)
  • 可以运行在html或node.js中
  • 基于BDD(行为驱动开发 TODO 和TDD的具体区别?)

基础念概

  • Suite 测试集(测试用例的集合)
    describe 声明
    可嵌套
  • Spec 测试用例,用 it 声明
  • Expectation 断言,用于比较真实值和期望值,得出测试结果
    expect 关键字
  • Matcher 匹配方法,是具体执行比较的函数,参数是期望值
    比如 toBe, toEqual, toMatch,
    可以用 not 修饰,比如 not.toBe(null), 现在可能用 toNotBe(null) 来代替了?
  • Setup与Teardown 搭建与拆除,每个测试用例运行前后,可以统一运行一些步骤
    比如每个用例里都要用到一些变量,则可以
    1. describe 里声明变量
    2. beforeEach 是给变量赋值
    3. afterEach 中收拾残局
  • 跳过测试,可以使用 xdescribexit

使用举例

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
describe("A test suite for SchoolService", function() { // 声明一个测试集
// 测试集中声明变量
var schoolService = new SchoolService(); // 这里生成service的方法就是简单的new一个
var teachers = [];
var students = [];

beforeEach(function() { // beforeEach中给变量赋值
teachers = schoolService.getTeachers();
students = schoolService.getStudents();
});

it("Spec test 1, test the getTeachers function", function() { // 声明一个测试用例
expect(teachers).not.toBe(null); // 断言的语法
expect(teachers.length).toEqual(5); // 断言的语法
});
it("Spec test 2: test the getStudents function", function() {
expect(students).not.toBe(null);
expect(students.length).toEqual(10);
});
it("Spec test 3: test the getTeacher function", function() {
var teacher = schoolService.getTeacher("011");
expect(teacher).not.toBe(null);
expect(teacher.name).toMatch(/teacher/);
expect(teacher.name).toMatch("teacher");
expect(teacher.name).not.toMatch(/people/);
var teacher6 = schoolService.getTeacher("016");
expect(teacher6).toBe(null);
});

afterEach(function() { // afterEach收拾残局
teachers = [];
students = [];
});
});

高级用法

  • 元数据测试
  • Timeout测试
  • 异步调用测试
  • spy概念
  1. spy相关

    将一个原有的部件,替换成听取测试框架指使的spy.以方便测试.
    比如可以

    1. 捏造返回值
    2. 监控被调用次数

    使用案例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 做好spy
    let serviceMethodSpy = spyOn(service, 'method');

    // 捏造返回值,在method被调用时,即可返回此处设置的值
    serviceMethodSpy.and.returnValue(1);

    // 监控被调用次数
    expect(serviceMethodSpy).toHaveBeenCalled();
    expect(serviceMethodSpy).toHaveBeenCalledTimes(1);

    另外也可以选择不保留 spyOn 的返回值,直接让method所属的service变成一个 SpyObj.
    通常的原因是

    1. 为了使用方便
    2. serivce还没有写好,但现在要用
    1
    2
    3
    4
    5
    const service: jasmine.SpyObj<Service> = jasmine.createSpyObj('Service', ['method']);
    // 捏造返回值
    service.method.and.returnValue(1);
    // 监控被调用次数
    expect(service.method).toHaveBeenCalled();
  2. 支持异步调用的测试

    web中有许多异步调用,那测试框架也需要能够支持这些异步代码的结果测试.
    正常想法,完全可以在 subscribe 中来进行断言操作,事实上也的确可以.

    jasmine可以三种方法来进行测试

    1. 让回调真的执行,不过要等待所有回调结束
    2. 使用一个假的模拟时间流逝的函数等待回调结束
    3. 对于非Observable或Promise的异步行为(比如js原生的setTimeout等),提供另外的方法
    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
    // 方法一
    it('should be get user list (async)', async(() => {
    // 调用被测试的方法
    fixture.whenStable().then(() => { // whenStable等待所有回调完毕
    fixture.detectChanges();
    expect(true).toBe(true);
    });
    }));

    // 方法二
    it('should be get user list (async)', fakeAsync(() => {
    // 调用被测试的方法
    tick(); // 模拟时间流逝,不再使用then,减少一层嵌套
    fixture.detectChanges();
    expect(true).toBe(true);
    }));

    // 方法三
    it('async demo', (done: () => void) => {
    context.show().subscribe(res => {
    expect(true).toBe(true);
    done(); // 第三种方法的核心? TODO
    });
    el.querySelected('xxx').click();
    });

karma工具

这些测试代码是js,它们需要一个能够运行的环境,
比如一个web页面,或者是一个node.js环境,
这个页面称为runner.

土一点的方法,甚至新建一个html文件,然后直接在script里写都行.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Jasmine Spec Runner v2.8.0</title>
<link rel="shortcut icon" type="image/png"
href="lib/jasmine-2.8.0/jasmine_favicon.png">
<link rel="stylesheet" href="lib/jasmine-2.8.0/jasmine.css">
<script src="lib/jasmine-2.8.0/jasmine.js"></script>
<script src="lib/jasmine-2.8.0/jasmine-html.js"></script>
<script src="lib/jasmine-2.8.0/boot.js"></script>
<!-- 引用被测试文件 -->
<script src="src/model.js"></script>
<!-- 引用测试代码 -->
<script src="spec/spec-test.js"></script>
</head>
<body>
</body>
</html>

高级一些的karma也提供这样的一个环境,
除了能够调用各种浏览器,
也可以基于node.js运行,因此能够在CI环境中使用.

使用karma时能够借助其命令行工具完成一些事情,
比如搭建测试框架并生成配置文件 (karma init之类的)

同时karma自己又有许多配置,文件放在 karma.conf.js
配置项包括且不限于

  1. 使用什么框架
  2. 是否使用 Require.js
  3. 默认使用什么浏览器
  4. 代码位置
  5. 文件的黑/白名单
  6. 是否监视文件的变化等等

protractor

在e2e测试时,一个刚需就是要模拟用户在浏览器上的操作.
e2e测试中的一个工具 protractor 也是Angular开发团队给出的工具.
该工具和单元测试一样,使用Jasmine测试框架来定义测试用例.
不同的是, protractor 提供了许多与页面相关的API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
browser.get(url)                // 访问具体地址
browser.close() // 关闭当前窗口
browser.sleep(ms) // 等待
browser.pause() // 暂停执行,主要用于调试

browser.manage().deleteAllCookies()
browser.managed().addCookie(key,value)

// 选择器, locator
by.id('myElement')
by.css('[class="element"]')
// 之后的这两个只在angularJs能用
by.binding('userInfo.realName')
by.model('something')

element.all(locator) // 使用选择器查找所有元素
element(locator) // 查找单个元素
element(by.id('user_name')).getText()
element(by.id('user_name')).sendKeys('user1') // 在输入框中输入'user1'
element(by.id('user_name')).sendKeys(protractor.Key.ENETER) // 在输入框中按下回车

更多的API可以参考 官方API文档

参考

  1. 前端测试框架介绍
  2. Jasmain和Karma初始介绍
  3. spy相关介绍
  4. protractor介绍