资讯专栏INFORMATION COLUMN

使用 AVA 做自动化测试

Cruise_Chan / 2384人阅读

摘要:单元测试,测试一个简单的组件。接口测试,用户信息接口测试。学习借鉴,一些使用做测试的开源项目。这里使用到的内置断言断言结果值等于我们想要的预期值,则测试通过。在里放入一个函数,函数自动执行,里面执行的结果必须抛出错误,则测试通过。

目录

1、为什么选择 AVA ?
2、API 概览。
3、准备工作。
4、单元测试,测试一个简单的工具函数。
5、使用 Promise、Async/await、Observable 。
6、使用 JSDOM 模拟浏览器环境。
7、单元测试,测试一个简单的 React 组件。
8、Http 接口测试,GitHub 用户信息接口测试。
9、串行测试。
10、快照断言。
11、覆盖率报告:nyc + Coveralls 。
12、持续集成:CircleCI 。
13、学习借鉴,一些使用 AVA 做测试的开源项目。
14、e2e测试框架推荐:TestCafe 。
15、参考。

为什么选择 AVA

原子测试 - 名词的链接属于自己猜测,不知作者本人是否也是表达这个意思。
断言 - 通俗的讲,就是用来判断 “ 函数的返回值 ” 与我们想要的值是否一致,一致则测试通过,不一致则不通过。

1、轻量,高效,简单。
2、并发测试,强制编写原子测试。
3、没有隐藏的全局变量,每个测试文件独立环境。
4、支持 ES2017,Promise,Generator,Async,Observable。
5、内置断言,强化断言信息。
6、可选的 TAP 输出显示。
7、为什么不用 Mocha,Tape,Tap?

官方文档解释:https://github.com/avajs/ava#faq

一些测试框架的对比:https://github.com/koajs/koa/...

API 概览
test([title], implementation)                     基本测试
test.serial([title], implementation)              串行运行测试
test.cb([title], implementation)                  回调函数形式
test.only([title], implementation)                运行指定的测试
test.skip([title], implementation)                跳过测试
test.todo(title)                                  备忘测试
test.failing([title], implementation)             失败的测试
test.before([title], implementation)              钩子函数,这个会在所有测试前运行
test.after([title], implementation)               钩子函数,这个会在所有测试之后运行
test.beforeEach([title], implementation)          钩子函数,这个会在每个测试之前运行
test.afterEach([title], implementation)           钩子函数,这个会在每个测试之后运行
test.after.always([title], implementation)        钩子函数,这个会在所有测试之后运行,不管之前的测试是否失败
test.afterEach.always([title], implementation)    钩子函数,这个会在每个测试之后运行,不管之前的测试是否失败
内置断言

也可以用 chai, node assert 等其他断言库

.pass([message])                                  测试通过
.fail([message])                                  断言失败
.truthy(value, [message])                         断言 value 是否是真值
.falsy(value, [message])                          断言 value 是否是假值
.true(value, [message])                           断言 value 是否是 true
.false(value, [message])                          断言 value 是否是 false
.is(value, expected, [message])                   断言 value 是否和 expected 相等
.not(value, expected, [message])                  断言 value 是否和 expected 不等
.deepEqual(value, expected, [message])            断言 value 是否和 expected 深度相等
.notDeepEqual(value, expected, [message])         断言 value 是否和 expected 深度不等
.throws(function|promise, [error, [message]])     断言 function 抛出一个异常,或者 promise reject 一个错误
.notThrows(function|promise, [message])           断言 function 没有抛出一个异常,或者 promise resolve
.regex(contents, regex, [message])                断言 contents 匹配 regex
.notRegex(contents, regex, [message])             断言 contents 不匹配 regex
.ifError(error, [message])                        断言 error 是假值
.snapshot(expected, [message])                    将预期值与先前记录的快照进行比较
.snapshot(expected, [options], [message])         将预期值与先前记录的快照进行比较
准备工作

务虚已过,编写测试用例之前我们需要先安装 AVA
先全局安装:npm i --global ava
再在项目根目录安装一次:npm i --save-dev ava
这是通俗的安装方式,全局安装方便 AVA 自身命令行调用,不用太纠结。

像我们刚刚说的,AVA 已经内置支持 ES2017 的语法,安装 AVA 的时候已经帮我们安装了一些关于 babel 的模块,不过我们还再安装几个我们需要用到的 babel 模块,如下。
npm i --save-dev babel-polyfill babel-preset-es2015 babel-preset-react babel-preset-stage-0

babel-polyfill                        // 包含 ES2015 及以后的功能函数,如:Object.assign
babel-preset-es2015                   // 支持 ES2015 语法
babel-preset-react                    // 支持 React 语法
babel-preset-stage-0                  // 支持 ECMA TC39 对 JS 语言定义的最早一个阶段的想法的语法

关于 AVA 的一些基础配置的意思,可以查看一下官方文档。
实际用到的配置也不多,我们在 package.json 文件中配置一下 AVA :

"scripts": {
  "test": "ava --verbose"             // 添加测试命令,方便我们直接输入一小段命令 npm test。--verbose 表示输出的测试信息尽量详细
},
"ava": {
  "babel": "inherit",                 // 继承已有的 babel 配置,就是继承我们下面 .babelrc 的文件配置
  "require": [                        // 每个测试前,先加载 require 里面的模块
    "babel-register",                 // 默认引入的,安装 AVA 时已经自带安装好
    "babel-polyfill"
  ]
}

在项目根目录创建 .babelrc 文件, 并输入以下内容:

这里的坑在于,如果不创建 .babelrc 文件,而是把 babel 的配置写在 package.json 里,在使用 import 导入 React 组件时,会报语法错误。
可使用命令行创建文件:touch .babelrc

{
  "presets": ["es2015", "stage-0", "react"]
}

看看现在的目录结构是怎么样的:

单元测试,测试一个简单的工具函数

test 目录创建一个 simple_test.js 文件,内容如下

import test from "ava";

function trimAll(string) {
    return string.replace(/[s]/g, "");
}

test("trimAll testing", t => {
    // 字符串内含有空格符、制表符等空字符都应删除
    t.is(trimAll(" 
 
 	 v  f B a r r  i  o  r  
  
  	  v    f  "), "Barrior");

    // 无空字符时,输出值应为输入值
    t.is(trimAll("Barrior"), "Barrior");

    // 输入 new String 对象应与输入基本类型字符串结果相同
    t.is(trimAll(new String(" T o m ")), "Tom");

    // 输入其他非字符串数据类型时,应抛出错误
    [undefined, null, 0, true, [], {}, () => {}, Symbol()].forEach(type => {
        t.throws(() => {
            trimAll(type);
        });
    });
});

test():执行一个测试,第一个参数为标题,第二参数为测试用例函数,接收一个包含内置断言 API 的参数 t,也是唯一一个参数;按照惯例这个参数名字叫做 t,没必要重新取名字。

这里使用到的内置断言:

t.is(resultValue, expected), 断言结果值等于我们想要的预期值,则测试通过。全等判断。

t.throws(function), 在 throws 里放入一个函数,函数自动执行,里面执行的结果必须抛出错误,则测试通过。

运行 npm test,可以看到如下结果,一个测试用例通过。

改动一下测试用例,看看测试不通过是怎么样的。

t.is(trimAll("Barrior123"), "Barrior");

运行 npm test

红色框框就是我们说的强化断言信息,将结果值预期值进行了差异对比,帮助我们定位错误。

使用 Promise、Async/await、Observable

PromiseAsync/await 都是语法层面的东西,Observable 还没深入了解过,
语法糖的代码就不贴来占用空间了,可以下载示例代码看看就会了。
Observable 这里的坑在于需要引入 RxJS: npm i --save rxjs,官方文档并没有说明。

import test from "ava";
import {Observable} from "rxjs";

test(t => {
    t.plan(3);
    return Observable
        .of(1, 2, 3, 4, 5, 6)
        .filter(n => {
            return n % 2 === 0;
        })
        .map(() => t.pass());
});
使用 JSDOM 模拟浏览器环境

安装 JSDOM 模块:npm i --save-dev jsdom

在目录下创建一个 jsdom.js 文件,内容如下:

import test from "ava";
import {JSDOM} from "jsdom";

const html = `




    
发布
    `; const {window} = new JSDOM(html, {runScripts: "dangerously"}); const document = window.document; test("emulate DOM environment with JSDOM", t => { const textarea = document.querySelector(".comment-box textarea"); const btn = document.querySelector(".btn"); const list = document.querySelector(".list"); const text = "hello world"; btn.click(); // 触发按钮的点击事件,此时文本框中没有输入内容 t.is(list.children.length, 0); // 列表应该保持为空 textarea.value = text; // 文本框中输入内容 btn.click(); // 触发按钮的点击事件 t.is(list.children.length, 1); // 此时列表的长度应该为 1 t.is(list.children[0].innerHTML, text); // 此时,第一个评论的内容应该等于刚刚我们输入的内容 t.falsy(textarea.value); // 评论完后,文本框应该清空 });

    简单介绍 JSDOM API

    new JSDOM(html, {runScripts: "dangerously"}); :创建一个 DOM 环境,可以传入完整的 HTML 文档,也可以值传入一行 HTML 文档声明,如:

    参数 runScripts: "dangerously" 表示让文档里的 JavaScript 可以运行,默认禁止运行。

    创建后返回一个对象,里面包含一个 window 对象,我们便是需要用到这个 window 对象,及其属性 document 对象,用在我们的测试。

    更多使用方法和配置可以查看一下官方文档。

    测试里面的代码就是原生的 JavaScript DOM 操作代码。

    单元测试,测试一个简单的 React 组件

    测试 React 组件需要依赖 JSDOM, 所以我们放在这里讲。
    安装需要依赖的一些模块:npm i --save react react-dom, npm i --save-dev enzyme react-test-renderer。这里也不用纠结为什么一会用 --save, 一会用 --save-dev, 因为 --save 表示这些模块在线上项目也需要用到,而 --save-dev 表示这些模块只用作开发或者测试等,线上项目不需要用到这些模块。
    Enzyme 是一个 React 测试工具,可以说是把 React 组件渲染在我们测试的环境里,不需要依赖真实的浏览器。
    Enzyme 依赖 react-test-rendererReact >=15.5 安装 react-test-renderer,其它版本安装 react-addons-test-utils

    src 目录下创建 todo.js 文件,内容如下,一个简单的备忘录组件:

    import React from "react";
    import ReactDOM from "react-dom";
    
    export default class Todo extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                names: props.names || []
            };
        }
    
        add() {
            const elem = this.refs.textarea;
            const name = elem.value;
            if (name) {
                elem.value = "";
                this.state.names.push(name);
                this.setState({});
            } else {
                elem.focus();
            }
        }
    
        del(i) {
            this.state.names.splice(i, 1);
            this.setState({});
        }
    
        render() {
            return (
                
      { this.state.names.map((name, i) => { return (
    • Member name: {name}
    • ) }) }
    ) } }

    test 目录下创建一个 helpers 文件夹,并在文件夹里面创建 setup_dom_env.js 文件, 内容如下。

    AVA 的规则会忽略 helpers 文件夹,不会将里面的文件当做测试文件执行。

    import {JSDOM} from "jsdom";
    const dom = new JSDOM("");
    global.window = dom.window;
    global.document = dom.window.document;
    global.navigator = dom.window.navigator;

    这就是 React 组件需要依赖的 JSDOM 模拟的 DOM 环境的代码。
    需要将 windowdocumentnavigator 等对象挂载到 global 对象上,组件才能运行。

    test 目录下创建 react_component.js, 内容如下,先引入模拟 DOM 环境的文件。

    import "./helpers/setup_dom_env";
    import test from "ava";
    import React from "react";
    import {mount} from "enzyme";
    
    import Todo from "../src/todo";
    
    test("actual testing for react component", t => {
        const wrapper = mount();  // 让组件运行,返回一个对象
    
        const list = wrapper.find("ul");                             // 从对象里找到 render 里的 DOM 元素 ul
        t.is(list.find("li").length, 2);                             // 断言备忘录有 2 条记录
    
        wrapper.find("textarea").node.value = "Lily";                // 文本框写入值
        wrapper.find("textarea + button").simulate("click");         // 触发按钮的点击事件
        t.is(list.find("li").length, 3);                             // 断言备忘录有 3 条记录
    });

    简单介绍 Enzyme API

    mount: 表示渲染组件的时候支持生命周期,个人觉得测试时一般都会用这个,因为真实组件生命周期的调用是极为平常的事。

    Enzyme APIjQuery API 很相似,会 jQuery 应该很容易理解。

    Http 接口测试,GitHub 用户信息接口测试

    打开接口:https://api.github.com/users/...,返回用户的一些基本信息,有些字段值是动态改变的,用户修改即变,这样的动态字段我们可以查询数据库来对比。这里我们以一个假设不变的 login 字段来演示。

    先安装 Request 模块: npm i --save-dev request,方便发送 http 请求。

    test 目录下创建 http.js, 内容如下。

    import test from "ava";
    import request from "request";
    
    // test.cb() 回调函数形式测试异步代码,异步结束调用 t.end()
    test.cb("http api testing", t => {
    
        // 基于 Request API 创建 http 请求的配置
        const options = {
            baseUrl: "https://api.github.com",
            url: "/users/Barrior",
            // 请求超时时间
            timeout: 5 * 1000,
            // http 请求头部,模拟得跟浏览器越像越好,不然被服务器处理成爬虫或者其他就可能得不到我们想要的响应
            headers: {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"
            }
        };
    
        // Request API 发送 GET 请求
        request.get(options, (err, res, body) => {
            if (err) t.fail("服务器响应超时!");
    
            if (res && res.statusCode === 200) {
                body = JSON.parse(body);
                t.is(body.login, "Barrior");
            } else {
                t.fail("无响应内容或状态码错误!");
            }
            
            // 异步结束
            t.end();
        });
    });

    运行 npm test,可以看到测试通过。

    串行测试

    很多情况并行测试就好,但某些场景我们需要测试按顺序一个接一个的执行,即使是异步,并且后面的测试可能依赖前面测试的结果,这时就需要用到串行测试,test.serial()

    test 目录下创建 serial.js, 内容如下,一个简单的串行测试演示。

    import test from "ava";
    
    const globalData = {};
    
    test.serial("serial testing: step one", t => {
        return new Promise(resolve => {
            setTimeout(() => {
                globalData.name = "Barrior";
                t.pass();
                resolve();
            }, 500);
        });
    });
    
    test("serial testing: step two", t => {
        t.is(globalData.name, "Barrior");
    });

    这里只是 serial.js 文件串行执行,如果想所有文件都串行执行,需要在命令行传递 --serial 标志。

    快照断言

    t.snapshot(expected, [options]), 将预期值与先前记录的快照进行比较。
    第一次运行测试,快照断言会将预期值存储起来,待第二次及以后运行测试,则拿已经存储好的快照与新的预期值进行比较,吻合则测试通过,否则测试失败。

    一般用于预期值比较庞大的情况,如:Html 模板,React 渲染出来的模板,或许还可以用于 Http 接口返回的一堆数据。

    如下,做个简单演示。

    import test from "ava";
    
    function getUserInfo(uid) {
        return [{
            id: 0,
            name: "Barrior",
            sex: "male"
        }, {
            id: 1,
            name: "Tom",
            sex: "male"
        }][uid]
    }
    
    function renderUserDom(uid) {
        const userInfo = getUserInfo(uid);
        return `
            
        `;
    }
    
    test("snapshot", t => {
        const user1 = renderUserDom(0);
        const user2 = renderUserDom(1);
    
        // 自定义 id 必须是一个字符串或者 buffer
        // 不定义,AVA 会默认生成一个 id
        t.snapshot(user1, {id: "1"});
        t.snapshot(user2, {id: "2"});
    });
    覆盖率报告:nyc + Coveralls

    安装模块 nyccoverallsnpm i --save-dev nyc coveralls
    扩展测试命令,前面加个 nyc 即可:"test": "nyc ava --verbose"
    测试覆盖率是基于文件被测试的情况来反馈出指标,所以我们把 simple_test.js 里的 trimAll 函数多带带提出来作为一个文件,放到 src 目录,命名为 trim_all.js

    运行 npm test,简洁的覆盖率报告如下。

    Stmts: Statement 的缩写,语句覆盖,通常指某一行代码是否被测试覆盖了,不包括注释,条件等。
    Branch: 分支覆盖或条件覆盖,指某一个条件语句是否被测试覆盖了,如:ifwhile;分支数是条件语句的两倍。
    Funcs: Function 的缩写,函数覆盖,指这个函数是否被测试代码调用了。
    Lines: 行覆盖,通常情况等于语句覆盖。一行未必只有一条语句(官方给的差异解释):https://github.com/gotwarlost...

    这里有一篇关于这几个指标的具体解释和演示说明,和对做覆盖率报告的思考:http://www.infoq.com/cn/artic...

    如果想看具体报告的信息,可以输出成 html 文档来瞧瞧,如下添加输出报告命令。

    "scripts": {
       ...
      "report": "nyc report --reporter=html"
    }

    运行 npm run reportcoverage 目录就会生成一些相关文件,浏览器打开 index.html,就可以看到如下内容。

    点击文件进去,可以查看该文件测试覆盖的详情。

    Coveralls

    一个将项目覆盖率展示到网页上,适合开源项目。
    网址:https://coveralls.io

    先注册登录,然后在项目根目录添加 .coveralls.yml,内容如下。

    service_name: travis-ci
    repo_token: 你自己的项目 token, Coveralls 网站提供的私有令牌

    添加上传命令。

    "scripts": {
       ...
      "coverage": "nyc report --reporter=text-lcov | coveralls"
    }

    运行 npm run coverage,等待报告上传完毕,就可以在网站上看到报告。

    持续集成:CircleCI

    通俗的讲,持续集成就是每次提交代码,自动化程序就自动构建(包括编译,发布,自动化测试等)来验证代码,从而尽早地发现代码中的错误。
    网址:https://circleci.com/,适合开源项目。

    在项目根目录添加 circle.yml 文件,内容如下,配置项都可以在文档中找到。

    # 配置 NodeJS 的版本为 7
    machine:
      node:
        version: 7
    
    # 安装依赖的命令
    dependencies:
      override:
        - npm i -g ava
        - npm i
    
    # 运行的测试命令
    test:
      override:
        - npm test

    使用 GitHub 账号登录 CircleCI 网站,选择持续集成这个项目,这里我们用的是 1.0 平台,不要选 2.0,因为配置的写法不一样。
    至此,每次提交代码到这个项目,CircleCI 就会自动帮我们集成。

    完成了覆盖率和持续集成,这两个网站都提供了小徽章给我们,类似如下,可以贴到项目中以显某种态度。

    学习借鉴,一些使用 AVA 做测试的开源项目

    pageres

    postcss-discard-comments

    postcss-selector-parser

    download

    jparticles

    e2e测试框架推荐:TestCafe

    官网地址:https://devexpress.github.io/...

    推荐理由(缺点须躬行):

    无需配置繁琐的环境。

    基于 NodeJS 生态。

    参考

    http://i5ting.github.io/ava-p...
    https://github.com/avajs/ava

    最后

    文中的代码托放于 GitHub,可供参考。

    文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/84508.html

    相关文章

    • 使用 ava 和 jsdom 前端测试

      摘要:前同事留下的测试,是基于浏览器的,主要还是功能测试。这里不详细说怎么在浏览器端使用测试了。而且作者也是建议和支持这样做的,简单明了的测试脚本,重要性有时候可能和测试本身一样重要。经测试,在浏览器也有这种问题。 2016-09-03 更新 随着在工作学习中更多地接触、使用测试工具,发现自己在本文中的一些记录是不准确、不正确的。 今天(九月三日)在家看了 NingJs 的直播,其中有一个分...

      GHOST_349178 评论0 收藏0
    • 基于 Babel 的 npm 包最小化设置

      摘要:翻译疯狂的技术宅原文本文首发微信公众号欢迎关注,每天都给你推送新鲜的前端技术文章本文描述了通过生成包的最小设置。是用于转换的预设。有关这两个属性的更多信息设置多平台包。表示使用上一节中的配置。结论以上是通过创建包最小库的方法。 翻译:疯狂的技术宅原文:http://2ality.com/2017/07/npm... 本文首发微信公众号:jingchengyideng欢迎关注,每天都...

      琛h。 评论0 收藏0
    • 提高代码质量——使用Jest和Sinon给已有的代码添加单元测试

      摘要:现在,我们可以使用单元测试来提高自己的代码质量。它在单元测试的编写中通常用来模拟等相关请求。通过这篇文章,你应该学会了如何针对已有代码从零开始编写一套完整的单元测试用例。 概述 在日常的功能开发中,我们的代码测试都依赖于自己或者QA进行测试。这些操作不仅费时费力,而且还依赖开发者自身的驱动。在开发一些第三方依赖的库时,我们也没有办法给第三方提供完整的代码质量报告。 现在,我们可以使用单...

      voyagelab 评论0 收藏0
    • 即将到来 Javascript 三个改变, 你会很喜欢它们的,因为确实是方便了很多!

      摘要:你将看到它们的语法时时关注它们的进展与更新。标准有个版本,个发布第个版本被放弃了。此建议的目的只是避免在起草建议被放弃或发生重大带来的麻烦。如果使用过度,将导致性能下降。在这个场景中,数字和空字符串都被认为是假的。 showImg(https://segmentfault.com/img/bVbj2Az?w=2000&h=1333); 想阅读更多优质文章请猛戳GitHub博客,一年百来...

      tinysun1234 评论0 收藏0
    • 2016-JavaScript之星

      摘要:在,是当之无愧的王者,赢得了与之间的战争,攻陷了的城池。于月发布了版本,这一版本为了更好的表现加入了渲染方式。前端框架这个前端框架清单可能是年疲劳的元凶之一。的创建者,目前在工作为寻找构建简单性和自主配置性之间的平衡做了很大的贡献。 春节后的第一篇就从这个开始吧~本文已在前端早读课公众号上首发 原文链接 JavasScript社区在创新的道路上开足了马力,曾经流行过的也许一个月之后就过...

      Binguner 评论0 收藏0

    发表评论

    0条评论

    Cruise_Chan

    |高级讲师

    TA的文章

    阅读更多
    最新活动
    阅读需要支付1元查看
    <