一、三个概念
BDD:行为驱动开发,你的所有开发时为了满足用户的某个行为
TDD:测试驱动开发,把产品经理的需求翻译成测试
assert:断言
二、chai
window自带一个很弱的断言库,我们采用chai库
JavaScript 中好像有断言工具函数,叫 console.assert(参数1,参数2)。
参数1:是断言条件,即应该发生的情况;
参数2:是不满足断言条件时,打印出来的提示信息。
当时提问忘了放出问题描述中我练习的断言小例子,很简单的,此处补充下:
console.assert(a === 3, "a 的值不是3!");下面三种断言都可以,前两种是BDD,后一种是TDD,体会区别
Should
chai.should();
foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.lengthOf(3);
tea.should.have.property('flavors')
.with.lengthOf(3);
Expect
var expect = chai.expect;
expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.lengthOf(3);
expect(tea).to.have.property('flavors')
.with.lengthOf(3);
Assert
var assert = chai.assert;
assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');
assert.lengthOf(foo, 3)
assert.property(tea, 'flavors');
assert.lengthOf(tea.flavors, 3);import chai from 'chai'
import Button from "./button";
import spies from 'chai-spies'
chai.use(spies)
const expect = chai.expect
// 单元测试
{
const Constructor = Vue.extend(Button)
const vm = new Constructor({
propsData: {
icon: 'settings'
}
})
vm.$mount() //动态的挂钩一个组件
let useElement = vm.$el.querySelector('use')
let href = useElement.getAttribute('xlink:href')
expect(href).to.eq('#i-settings')
vm.$el.remove()
vm.$destroy()
}
{
const Constructor = Vue.extend(Button)
const vm = new Constructor({
propsData: {
icon: 'settings',
loading: true
}
})
vm.$mount()
let useElement = vm.$el.querySelector('use')
let href = useElement.getAttribute('xlink:href')
expect(href).to.eq('#i-loading')
vm.$el.remove()
vm.$destroy()
}
{
const div = document.createElement('div')
document.body.appendChild(div)
const Constructor = Vue.extend(Button)
const vm = new Constructor({
propsData: {
icon: 'settings'
}
})
vm.$mount(div)
let svg = vm.$el.querySelector('svg')
let {order} = window.getComputedStyle(svg)
expect(order).to.eq('1')
vm.$el.remove()
vm.$destroy()
}
{
const div = document.createElement('div')
document.body.appendChild(div)
const Constructor = Vue.extend(Button)
const vm = new Constructor({
propsData: {
icon: 'settings',
iconPosition: 'right'
}
})
vm.$mount(div)
let svg = vm.$el.querySelector('svg')
let {order} = window.getComputedStyle(svg)
expect(order).to.eq('2')
vm.$el.remove()
vm.$destroy()
}
{
// mock
const Constructor = Vue.extend(Button)
const vm = new Constructor({
propsData: {
icon: 'settings',
}
})
let spy = chai.spy(function(){})
vm.$on('click', spy)
// 希望这个函数被执行
let button = vm.$el
button.click()
expect(spy).to.have.been.called()
}我们原本是直接new Vue({}).$mount()的现在我们new Constructor({}).$mount(),template里面的代码和button里面的代码完全是一个东西
单元测试就是你输入一个东西,你在输出力看是否匹配,匹配就是成功了
把上面的代码try...catch,把错误打出来
三、Karma自动化单元测试
- Karma(
[ˈkɑrmə]卡玛)是一个测试运行器,它可以呼起浏览器,加载测试脚本,然后运行测试用例 - Mocha(
[ˈmoʊkə]摩卡)是一个单元测试框架/库,它可以用来写测试用例 - Sinon(西农)是一个 spy / stub / mock 库,用以辅助测试(使用后才能理解)
我们可以用karma库运行npm run test:
打包我们的js,打开浏览器,运行网页,运行完网页后自动关闭浏览器,把浏览器输出显示到终端
- 安装karma
npm i -D karma karma-chrome-launcher karma-mocha karma-sinon-chai mocha
sinon sinon-chai karma-chai karma-chai-spies- 创建 karma 配置
// 新建 karma.conf.js//如下
module.exports = function(config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: "", // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ["mocha", "sinon-chai"], client: { chai: { includeStack: true } }, // 要在浏览器打开什么文件,dist下的所有js和css文件 files: ["dist/**/*.test.js", "dist/**/*.test.css"], // list of files / patterns to exclude exclude: [], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: {}, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ["progress"], // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: true, //打开哪个浏览器 browsers: ["Chrome"], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false, // Concurrency level // how many browser should be started simultaneous concurrency: Infinity }); };创建 test/button.test.js 文件(一会下面会写)
创建测试脚本在 package.json 里面找到 scripts 并改写 scripts
"scripts": { "dev-test": "parcel watch test/* --no-cache & karma start", "test": "parcel build test/* --no-cache --no-minify && karma start --single-run" },
具体含义parcel build test/*,不缓存不压缩打包test下面的所有一级文件,打包到dist目录下,karma start --single-run"启动karma只运行一次
dev-test:每次代码修改后自动测试
- 运行成功
dist\button.test.js.map ‼ 1.34 MB 319ms
dist\button.test.js 877.61 KB 7.58s
dist\button.test.css 1.12 KB 6.93s
15 04 2020 21:34:44.378:INFO [karma-server]: Karma v5.0.1 server started at http://0.0.0.0:9876/
15 04 2020 21:34:44.384:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
15 04 2020 21:34:44.396:INFO [launcher]: Starting browser ChromeHeadless
15 04 2020 21:34:47.863:INFO [Chrome Headless 72.0.3626.121 (Windows 10)]: Connected on socket oAtefySifMYrIYfBAAAA with id 67609423
Chrome Headless 72.0.3626.121 (Windows 10): Executed 5 of 5 SUCCESS (0.003 secs / 0.013 secs)
TOTAL: 5 SUCCESS- 小bug,由于我的g-icon组件在全局注册(index.js),我在button组件里,都是直接用g-icon,所以测试button.test的时候没有可见的g-icon依赖,所以test失败,尽量改成局部注册组件
四、Mocha和Chai的单元测试
在刚才我们只用Chai做测试
{
const Constructor = Vue.extend(Button)
const vm = new Constructor({
propsData: {
icon: 'settings'
}
})
vm.$mount() //动态的挂钩一个组件
let useElement = vm.$el.querySelector('use')
let href = useElement.getAttribute('xlink:href')
expect(href).to.eq('#i-settings')
vm.$el.remove()
vm.$destroy()
}重要两点
1.用块把每个测试隔开,防止变量冲突,chai是用{}分隔的,mocha用it函数隔开的,如下第一个参数是名字可读性比较好,第二个是代码
2. 写一个assert
describe("Button", () => {
it("存在.", () => {
expect(Button).to.be.ok;
});
it("可以设置icon.", () => {
const Constructor = Vue.extend(Button);
const vm = new Constructor({
propsData: {
icon: "setting"
}
}).$mount();
const useElement = vm.$el.querySelector("use");
expect(useElement.getAttribute("xlink:href")).to.equal("#i-setting");
vm.$destroy();
})
})描述了一个Button,它有的各种东西,这就很BDD,行为驱动
it("点击 button 触发 click 事件", () => {
const Constructor = Vue.extend(Button);
const vm = new Constructor({
propsData: {
icon: "setting"
}
}).$mount();
const callback = sinon.fake();
vm.$on("click", callback);
vm.$el.click();
expect(callback).to.have.been.called;
});由于我们很难判断一个函数是否被调用,所以我们用sinon库来判断,sinon.fake()知道自己有没有被调用,和chai.spy()作用一样
chai的很多语法都是虚词,具体看文档,例如两个数组比较时分清楚深拷贝和浅拷贝
1. karma库,自动化
2. chai库,具体assert
3. mocha库,作用域隔离,BDD描述风格
4. sinon库,spy函数
五、TravisCI持续集成
我们一开始要自己打开 Chrome 测试我们的代码,后来使用 Karma 可以做到一行命令测试我们的代码。再自动化一点,就是持续集成-持续测试,持续交付,持续部署
Travis CI 提供的是持续集成服务(Continuous Integration,简称 CI)。它绑定 Github 上面的项目,只要有新的代码,就会自动抓取。然后,提供一个运行环境,执行测试,完成构建,还能部署到服务器。
持续集成指的是只要代码有变更,就自动运行构建和测试,反馈运行结果。确保符合预期以后,再将新代码"集成"到主干。
持续集成的好处在于,每次代码的小幅变更,就能看到运行结果,从而不断累积小的变更,而不是在开发周期结束时,一下子合并一大块代码。
1. 创建.travis.yml
2.
language: node_js
node_js:
-"8"
addons:
chrome: stable
sudo: required
before_script:
-"sudo chown root /opt/google/chrome/chrome-sandbox"
-"sudo chmod 4755 /opt/google/chrome/chrome-sandbox"3. 每次push就会测试
六、npm
1. npm adduser
2. npm publish
3. 新开项目,npm install自己的包
4. x-link把包和项目连接起来,这样不用反复的下载install
使用过程中我们发现报错说 import 语法有问题,那时因为 node 目前确实不支持 import,我们需要用 babel 转译 import
- 你可以要求使用者自己用 babel 来转译
- 你也可以转义好了再给他用
npx parcel build index.js --no-minify(本来不应该加 --no-minify 的,奈何不加会有 bug,HTML 压缩把 slot 标签全删了),打包到dist下index.js import Button from './src/button' import ButtonGroup from './src/button-group' import Icon from './src/icon' export {Button,ButtonGroup,Icon}- 将 package.json 的 main 改为 dist/index.js,用户下载dist下的文件
使用 npm link 或者 yarn link 来加速调试你每次修改源码之后,有两条路让别人得到最新效果
1. 更新 package.json 里的 version,然后 npm publish。别人通过 npm update xxx 来更新。
2. 如果你只是为了本地调试,可以在项目目录使用 npm link,然后在使用之处运行 npm link xxx,就是最新了
七、约定
测试框架都有一个约定,不执行异步语句,默认把所有代码当成同步执行,或者加done参数