NutUI 3.0 中单元测试的探索和实践

avatar
UX @京东

众所周知,单元测试功能,是组件库开发中必不可少的一部分,负责进行检查和验证,保证了组件的合理性和规范性。本文主要讲的就是单元测试在 NUTUI 组件库 中的探索实践,我们将从如何编写单元测试、持续集成服务、Coveralls 自动测试代码覆盖率三方面进行阐述。如图所示:

image

如果你对这些内容感兴趣,就和我一起来看一下吧!

单元测试配置

在进入单元测试配置正文之前,我们先来了解下面两个问题。

  • 单元测试是什么?
  • 为什么需要单元测试?

单元测试是什么?

单元测试(unit testing),可以对软件中的最小可测试单元进行检查和验证,是软件开发中重要的一部分。它使得添加新功能和追踪问题更加容易。

为什么需要单元测试?

单元测试在开发的时候很有用,即能帮助开发者思考如何设计一个组件,也能够重构一个现有组件。每次代码发生变化的时候它们都会被运行。有了单元测试,我们可以自信的交付自己的代码,而没有任何的后顾之忧。 组件的单元测试有以下等优点:

  • 提供描述组件行为的文档
  • 减少调试时间,节省手动测试的时间
  • 减少研发新特性时产生的 bug,测出功能的隐藏 bug
  • 减少和快速定位 bug
  • 促进重构,保证代码重构的安全性

如何编写单元测试?

我们既是单元测试的受益者,同时也是开发者,接下来我们进入正题,来聊一聊,如何在 vue 组件库中,加入单元测试。 单元测试用到的工具大致分为三部分:测试框架、测试运行器、断言库。

测试框架

因为我们是 vue 组件库,所以使用 Vue Test Utils 作为测试框架,它是 Vue 组件单元测试的官方库,有详细的指引和自定义设置用于测试,文档清晰,容易上手。

我们将其作为开发依赖安装在项目中:

npm install --save-dev @vue/test-utils

它依赖浏览器环境,可以运行在真实的浏览器或是 Node 虚拟浏览器中,因为在不同的平台上启动真实的浏览器是比较复杂的。所以我们让其运行在 Node 虚拟浏览器中,这就需要借助 JSDOM 帮助我们让它在 Node 虚拟浏览器环境运行测试。

我们把 JSDOM 做为开发依赖安装:

npm install --save-dev jsdom jsdom-global

然后在项目根目录创建 /test 的空文件夹,创建 setup.js 文件

require('jsdom-global')()

在测试入口处使用 jsdom-global 手动设置 JSDOM 即可。这样每次运行单元测试都会执行 setup.js 文件,从而引入 JSDOM。

测试运行器

测试运行器(test runner)就是运行测试的程序。测试运行器很多,Vue Test Utils 基本上可以支持主流 JavaScript 测试运行器。比较好的是,Vue Test Utils 帮我们筛选了一遍,推荐了两个测试运行器给我们,我们从中选择一个即可,

  • Jest: 功能最全的测试运行器。它所需的配置是最少的,默认安装了 JSDOM,内置断言且命令行的用户体验非常好。
  • mocha-webpack: webpack + Mocha 的包裹器,包含顺畅的接口和侦听模式。

测试单文件组件的策略是通过 webpack 编译所有的测试文件,然后在测试运行器中运行,使用 mocha-webpack 好处在于我们能够通过 webpack + vue-loader 得到完整的单文件组件支持,我们不必对源代码做任何妥协,虽然 Jest 也提供了vue-jest 预处理器来处理最常见的单文件组件,但仍不是 vue-loader 100% 的功能。所以我们选用了 mocha-webpack。

通过 npm 安装 mocha、mocha-webpack:

npm install --save-dev mocha mocha-webpack

需要注意的是,mocha-webpack 依赖 webpack 和 mocha,并且对版本有要求 webpack 版本需要4.x.x,mocha 版本为4.x.x & 5.x.x。

断言库

我们使用 mocha 进行测试的时候,需要结合断言库去使用,Mocha 不像 Jest 框架一样有内置的断言库。它允许我们自己选择合适的断言库。expect 是一款断言库,它极简的 BDD 风格,得到很多测试框架的认可,这些测试框架内置的也是 expect 断言库,因此我们本次也使用了 expect 断言库。

首先安装开发依赖:

$ npm install --save-dev expect

并在 test/setup.js 中写入:

global.expect = require('expect')

令其全局可用,这样就不需要在每个测试文件里导入它了。

安装好了各种开发依赖之后,在根目录 package.json 文件中定义定义一个 npm 脚本 test 命令

{
    ...
    "scripts": {
        ...
        "test": "cross-env NODE_ENV=test mocha-webpack --webpack-config dist_cli/webpack/test.config.js  --require dist_cli/test/setup.js src/packages/*/__test__/**.spec.js"
    },
    ...
}

值得注意的是,如果项目没有安装 cross-env,需要先安装一下,它用于跨平台设置环境变量。 简单来了解一下 test 命令参数的含义:

  • --webpack-config:指定了该测试使用的 webpack 配置文件。
  • --require:确保了文件 test/setup.js 会在任何测试之前运行,这样我们可以在该文件中设置测试所需的全局环境。
  • 最后一个参数 src/ 目录:是该测试包所涵盖的所有测试文件的集合。

准备好以上环境,在命令行执行 npm test 就可以执行测试。 效果如下:

image

最基本的单元测试已经配置完成,但我们的工作还没有结束,继续往下看

增加单元测试代码覆盖率

覆盖率既是度量测试完整性的一个手段,也是测试有效性的一个度量。测试覆盖是对测试完全程度的评测。 Mocha 是 JavaScript 项目的测试工具,Istanbul 是 JS 测试覆盖率报告的生成工具。我们利用二者测试代码并生成代码库的测试覆盖率报告。

nyc 是 Istanbul 的命令行接口,我们将其作为开发依赖安装在项目中:

$ npm install --save-dev nyc 

然后在上面的 npm 脚本增加 nyc

{
    ...
    "scripts": {
        ...
        "test": "cross-env NODE_ENV=test nyc mocha-webpack --webpack-config dist_cli/webpack/test.config.js  --require dist_cli/test/setup.js src/packages/*/__test__/**.spec.js"
    },
    ...
}

同时需要在的 package.json 中配置 nyc:

{
    ...
    "nyc": {
    "include": [
        "../../../src/packages/**/*.vue"
    ],
    "reporter": [
        "lcov",
        "text"
    ],
    "instrument": false,
    "sourceMap": false
    },
    ...
}

介绍一下参数的含义:

  • include: 测试文件路径
  • reporter: 输出 lcov (lcov.info + html 报告)和文本形式的覆盖率报告
  • instrument 和 sourceMap: 设置为 false,禁用 nyc 去 instrumenting 和 sourceMap 你的代码,后续我们指定 loader 去完成。

这时我们运行 npm test 命令, 就可以获得代码库的测试覆盖率报告。

image

如图所示,出现了多个 Unknow,显然还有一些问题,需要安装一下 istanbul-instrumenter-loader,然后把 loader 加到我们的 test.config.ts 的配置文件中。再把生成环境的配置文件 package.conf.ts 导入 merge 进去。

import { ROOT_PACKAGE_PATH } from '../common/dic';
import { packageConfig } from './package.config';
import merge from 'webpack-merge';
module.exports =  merge(packageConfig(false), {
    module: {
        rules: [
            {
                test: /\.(js|ts)/,
                use: {
                    loader: 'istanbul-instrumenter-loader',
                    options: { esModules: true }
                },
                include: [ROOT_PACKAGE_PATH('src/packages')]
            },
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader', 
                ]
            },
            {
                test: /\.scss$/,
                use: [
                    'style-loader',
                    'css-loader',
                    {
                        loader: 'sass-loader',
                        options: { 
                            prependData: `@import "@/styles/index.scss"; `
                        }
                    }
                ]
            }
        ],
    },
    devtool: 'inline-cheap-module-source-map',
    externals: [require('webpack-node-externals')()] // 忽略node_modules文件夹中的所有模块
});

至此,整个项目的代码覆盖率统计配置基本完成,值得注意的是 istanbul-instrumenter-loader 需要放在最上面,保证它最后执行,这时我们在终端执行 npm test 会显示测试覆盖率结果。

image

同时,在项目根目录,会自动创建 coverage 文件夹,里面有生成测试覆盖率报告文件 (Icov.info+html)。

image

接下来我们还要介绍一下持续集成服务

持续集成服务

持续集成指的是只要代码有变更,就自动运行构建和测试,反馈运行结果。确保符合预期之后,再将新代码集成到主干, 有助于提高项目质量。通过持续集成可以自动编译、打包、签名项目,配合单元测试可以实现持续集成+自动化测试。我们的项目维护在 Github 上。Github 有个好朋友 Travis CI,是一个在线托管的分布式持续集成服务,我们可以用它来构建并测试托管在 Github 上的软件项目。你可以轻松用 Travis CI 同步你的 Github 项目,并且你可以在几分钟内就能测试你的项目。

值得注意的是,Travis CI 分为免费和自费的,对于我们 Github 上的开源项目,直接可以用免费的,访问 免费版 即可。

下面来看一下怎么让自己的 Github 项目用 Travis CI 测试。

首先,访问 Travis CI 并使用项目的 Github 账户登录。 登录之后,代表你的 Github 对 Travis 进行了授权,Travis 可以访问你的 Github 上所有代码仓库。选择需要 Travis CI 帮你持续集成的仓库,点击右侧的激活开关即可。这样Travis CI 就会帮你监听这个仓库的所有变化自动构建,完成预定的操作。

接下来,我们需要在我们代码仓库的根目录添加一个 .travis.yml 文件,来告诉 Travis CI 定义预定的命令,它会告诉 Travis CI 做什么,怎么做。配置如下内容,可按需更改:

sudo: required
language: node_js
node_js:
- '8'
script:
- npm test
- npm run coveralls

Travis 的运行流程很简单,包含两个阶段:install(安装依赖)和 script(执行脚本)。对于 Node 项目来说,install 和 script 阶段都有默认脚本,如不需要修改,可以省略不写。 了解一下参数的含义:

  • sudo: required 表示需要 sudo 权限。
  • language: 指定了项目的语言,node_js 指定运行环境为 Node 。
  • node_js: 指定 Node 版本,可指定多个。
  • install: 安装依赖,用来指定安装脚本,的默认脚本是 npm install
  • script: 用来指定构建或测试脚本, 默认脚本 npm test。

还有很多参数,我就不一一列举了,具体可以查看官网配置

完成上面的操作后,push 这个这个文件到你的 Github 仓库。之后每次往 Github 的该仓库 push 代码,Travis 就会去找这个文件,执行配置好的预定义指令了。

image

这里可以看到运行结果,可点击查看构建过程的详细信息。如运行有误,会有如下提示,我们要知道,script构建阶段只要有一个失败,状态就会显示失败。

image

以上就是简单的 Travis CI 和 Github 项目的关联过程。

Travis CI 确实很给力,如果想在 Github 项目中直接看到 CI 结果徽标,只需要点击该图标,选择 markdown,然后将 result 文本框内容复制到 Github 上的 README.md 文件中即可。

image

Coveralls自动测试代码覆盖率

最后,我们想要生成一份代码覆盖率的报告,这里需要使用 Coveralls。Coveralls 支持 Github 上的项目,也可以与 Travis CI集成。 访问 coveralls.io/,使用 Github 账号登录,之后点击上面的 Add Repo,接着将按钮置为 ON 状态

image

点击右边 detail,点击 detials 进入详细配置页面,页面右侧,获取该项目的 token,根据自己的环境类型编辑相应配置文件 在根目录下添加 .coverall.yml 文件,并添加下面内容:

service_name: travis-ci
repo_token: bOzghLfr6hi9x**************56vdl1YG

给 Coveralls 上传的测试报告需要有统一的 lcov 格式,上文中我们有对 nyc 进行配置,在根目录 coverage 文件夹中生成 lcov.info 在 package.json 文件的 scripts 字段添加下面这行命令

 "coveralls": "cat ./coverage/lcov.info | coveralls",

push 代码到Github仓库。 同样,我们可以获取一个测试覆盖率的徽标,进入刚才的详情配置页。点击按钮复制 markdown 内容到 Github 上的 README.md 文件中即可。

image

总结

单元测试功能已集成在由我们团队开发的 NUTUI 组件库 上面进行实践,NutUI 是一套京东风格的移动端Vue组件库,开发和服务于移动 Web 界面的企业级前中后台产品。通过 NutUI,可以快速搭建出风格统一的页面,提升开发效率。目前已有近 50 个组件,这些组件被广泛使用于京东的各个移动端业务中。后期我们会对整个 NutUI 系统架构进行革新,将整个组件库构建工具抽离出,采用 WebPack Node API构建,对编译做出更细粒度的控制,同时加大对编译配置的优化调整,大幅提高性能和减少打包文件体积,提供独立构建的 NutUI-CLI。单元测试的功能也保证了组件的规范化,减少错误。欢迎各位使用,如在使用中有任何问题,我们也会及时反馈跟进。流年笑掷,未来可期~

image

最后,以上是 NUTUI 组件库 官方网址,欢迎扫码体验~