記錄一些在爲項目引入單元測試時的一些困惑,但願能夠對社區的小夥伴們有所啓迪,少走一些彎路少踩一些坑。javascript
如何爲聊天的文字消息組件寫單元測試?html
名詞 | Github描述 | 我的理解 |
---|---|---|
jest | Delightful JavaScript Testing. Works out of the box for any React project.Capture snapshots of React trees | facebook家的測試框架,與react打配合會更加駕輕就熟一些。 |
mocha | Simple, flexible, fun JavaScript test framework for Node.js & The Browser | 強大的測試框架,中文名叫抹茶,常見的describe,beforeEach就來自這裏 |
karma | A simple tool that allows you to execute JavaScript code in multiple real browsers. Karma is not a testing framework, nor an assertion library. Karma just launches an HTTP server, and generates the test runner HTML file you probably already know from your favourite testing framework. | 不是測試框架,也不是斷言庫,能夠作到抹平瀏覽器障礙式的生成測試結果 |
chai | BDD / TDD assertion framework for node.js and the browser that can be paired with any testing framework. | BDD/TDD斷言庫,assert,expect,should比較有趣 |
sinon | Standalone and test framework agnostic JavaScript test spies, stubs and mocks | js mock測試框架,everything is fake,spy比較有趣 |
jsmine | Jasmine is a Behavior Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework. Thus it's suited for websites, Node.js projects, or anywhere that JavaScript can run. | js BDD測試框架 |
vue/test-utils | Utilities for testing Vue components | 專門爲測試單文件組件而開發,學會使用vue-test-utils,將會在對vue的理解上更上一層樓 |
經過上述的表格,做爲一個vue項目,引入單元測試,大體思路已經有了:前端
測試框架:mocha 抹平環境:karma 斷言庫:chai BDD庫:jsmine
這並非最終結果,測試vue單文件組件,固然少不了vue-test-utils,可是將它放在什麼位置呢。
須要閱讀vue-test-utils源碼。vue
const chai = require('chai'); const assert = chai.assert; const expect = chai.expect(); const should = chai.should();
function once(fn) { var returnValue, called = false; return function () { if (!called) { called = true; returnValue = fn.apply(this, arguments); } return returnValue; }; }
it('calls the original function', function () { var callback = sinon.fake(); var proxy = once(callback); proxy(); assert(callback.called); });
只調用一次更重要:java
it('calls the original function only once', function () { var callback = sinon.fake(); var proxy = once(callback); proxy(); proxy(); assert(callback.calledOnce); // ...or: // assert.equals(callback.callCount, 1); });
並且咱們一樣以爲this和參數重要:node
it('calls original function with right this and args', function () { var callback = sinon.fake(); var proxy = once(callback); var obj = {}; proxy.call(obj, 1, 2, 3); assert(callback.calledOn(obj)); assert(callback.calledWith(1, 2, 3)); });
once返回的函數須要返回源函數的返回。爲了測試這個,咱們建立一個假行爲:react
it("returns the return value from the original function", function () { var callback = sinon.fake.returns(42); var proxy = once(callback); assert.equals(proxy(), 42); });
一樣還有 Testing Ajax,Fake XMLHttpRequest,Fake server,Faking time等等。webpack
test spy是一個函數,它記錄全部的參數,返回值,this值和函數調用拋出的異常。
有3類spy:ios
測試函數如何處理一個callback。git
"test should call subscribers on publish": function() { var callback = sinon.spy(); PubSub.subscribe("message", callback); PubSub.publishSync("message"); assertTrue(callback.called); }
用spy包裹一個存在的方法。sinon.spy(object,"method")
建立了一個包裹了已經存在的方法object.method的spy。這個spy會和源方法同樣表現(包括用做構造函數時也是如此),可是你能夠擁有數據調用的全部權限,用object.method.restore()能夠釋放出spy。這裏有一我的爲的例子:
{ setUp: function () { sinon.spy(jQuery, "ajax"); }, tearDown: function () { jQuery.ajax.restore();// 釋放出spy }, }
What’s the difference between Unit Testing, TDD and BDD?
[[譯]單元測試,TDD和BDD之間的區別是什麼?](https://github.com/FrankKai/F...
SO上有這樣一個問題:What does 「spec」 mean in Javascript Testing
spec是sepcification的縮寫。
就測試而言,Specification指的是給定特性或者必須知足的應用的技術細節。最好的理解這個問題的方式是:讓某一部分代碼成功經過必須知足的規範。
test文件夾下便可,文件名以.spec.js結尾便可,經過files和preprocessors中的配置能夠匹配到。
看不懂karma.conf.js,到 http://karma-runner.github.io... 學習配置。
const webpackConfig = require('../../build/webpack.test.conf'); module.exports = function karmaConfig(config) { config.set({ browsers: ['PhantomJS'],// Chrome,ChromeCanary,PhantomJS,Firefox,Opera,IE,Safari,Chrome和PhantomJS已經在karma中內置,其他須要插件 frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],// ['jasmine','mocha','qunit']等等,須要額外經過NPM安裝 reporters: ['spec', 'coverage'],// 默認值爲progress,也能夠是dots;growl,junit,teamcity或者coverage須要插件。spec須要安裝karma-spec-reporter插件。 files: ['./index.js'],// 瀏覽器加載的文件, `'test/unit/*.spec.js',`等價於 `{pattern: 'test/unit/*.spec.js', watched: true, served: true, included: true}`。 preprocessors: { './index.js': ['webpack', 'sourcemap'],// 預處理加載的文件 }, webpack: webpackConfig,// webpack配置,karma會自動監控test的entry points webpackMiddleware: { noInfo: true, // webpack-dev-middleware配置 }, // 配置reporter coverageReporter: { dir: './coverage', reporters: [{ type: 'lcov', subdir: '.' }, { type: 'text-summary' }], }, }); };
結合實際狀況,經過https://vue-test-utils.vuejs.... 添加切合vue項目的karma配置。
demo地址:https://github.com/eddyerburg...
karma.conf.js
// This is a karma config file. For more details see // http://karma-runner.github.io/0.13/config/configuration-file.html // we are also using it with karma-webpack // https://github.com/webpack/karma-webpack const webpackConfig = require('../../build/webpack.test.conf'); module.exports = function karmaConfig(config) { config.set({ // to run in additional browsers: // 1. install corresponding karma launcher // http://karma-runner.github.io/0.13/config/browsers.html // 2. add it to the `browsers` array below. browsers: ['Chrome'], frameworks: ['mocha'], reporters: ['spec', 'coverage'], files: ['./specs/*.spec.js'], preprocessors: { '**/*.spec.js': ['webpack', 'sourcemap'], }, webpack: webpackConfig, webpackMiddleware: { noInfo: true, }, coverageReporter: { dir: './coverage', reporters: [{ type: 'lcov', subdir: '.' }, { type: 'text-summary' }], }, }); };
test/unit/specs/chat.spec.js
import { mount } from '@vue/test-utils'; import { expect } from 'chai'; import ChatText from '@/pages/chat/chatroom/view/components/text'; describe('ChatText.vue', () => { it('人生中第一次單元測試:', () => { const wrapper = mount(ChatText); console.log(wrapper.html()); const subfix = '</p> <p>默認文字</p></div>'; expect(wrapper.html()).contain(subfix); }); });
注意,被測試組件必須以index.js暴露出組件。NODE_ENV=testing karma start test/unit/karma.conf.js --single-run
測試結果:
1.PhantomJS是什麼?
下載好phantomjs後,就能夠在終端中模擬瀏覽器操做了。
foo.js
var page = require('webpage').create(); page.open('http://www.google.com', function() { setTimeout(function() { page.render('google.png'); phantom.exit(); }, 200); });
phantomjs foo.js
運行上述代碼後,會生成一張圖片,可是畫質感人。
2.karma-webpack是什麼?
在karma中用webpack預處理文件。
啥玩意兒???一一搞定。
this.vm.$options.propsData // 組件的自定義屬性,是由於2.1.x版本中沒有$props對象,https://vue-test-utils.vuejs.org/zh/api/wrapper/#setprops-props const elm = options.attachToDocument ? createElement() : undefined // "<div>" or undefined slots // 傳遞一個slots對象到組件,用來測試slot是否生效的,值能夠是組件,組件數組或者字符串,key是slot的name mocks // 模擬全局注入 stubs // 存根子組件
後知後覺,這些均可以在Mounting Options文檔查看:https://vue-test-utils.vuejs....
mount僅僅掛載當前組件實例;而shallowMount掛載當前組件實例之外,還會掛載子組件。
這是一個一直困擾個人問題。
測試通用業務組件?業務變動快速,單元測試波動較大。❌
測試用戶行爲?用戶行爲存在上下文關係,組合起來是一個很恐怖的數字,這個交給測試人員去測就行了。❌
那我到底該測什麼呢?要測試功能型組件,vue插件,二次封裝的庫。✔️
就拿我負責的項目來講:
功能型組件:可複用的上傳組件,可編輯單元格組件,時間選擇組件。(前兩個組件都是老大寫的,第三個是我實踐中抽離出來的。)
vue插件:mqtt.js,eventbus.js。(這兩個組件是我抽象的。)
二次封裝庫:httpclient.js。(基於axios,老大初始化,我添磚加瓦。)
上述適用於單元測試的內容都有一個共同點:複用性高!
因此咱們在糾結要不要寫單元測試時,抓住複用性高這個特色去考慮就行了。
單元測試是爲了保證什麼呢?
因此,其實單元測試是爲了幫助開發者的突破本身心裏的最後一道心理障礙,創建老子的代碼徹底ojbk,不可能出問題的自信。
其實最終仍是保證用戶有無bug的組件可用,有好的軟件或平臺使用,讓本身的生活變得更加美好。
/* title: vue插件eventbus單測 author:frankkai target: 1.Vue.use(eventBus)是否能夠正確注入$bus到prototype 2.注入的$bus是否能夠成功掛載到組件實例 3.$bus是否能夠正常訂閱消息($on)和廣播消息($emit) */ import eventbusPlugin from '@/plugins/bus'; import { createLocalVue, createWrapper } from '@vue/test-utils'; import { expect } from 'chai'; const localVue = createLocalVue(); localVue.use(eventbusPlugin); const localVueInstance = (() => localVue.component('empty-vue-component', { render(createElement) { return createElement('div'); }, }))(); const Constructor = localVue.extend(localVueInstance); const vm = new Constructor().$mount(); const wrapper = createWrapper(vm); describe('/plugins/bus.js', () => { it('Vue.use(eventBus)是否能夠正確注入$bus到prototype:', () => { expect('$bus' in localVue.prototype).to.equal(true); }); it('注入的$bus是否能夠成功掛載到組件實例:', () => { expect('$bus' in wrapper.vm).to.equal(true); }); it('$bus是否能夠正常訂閱消息($on)和廣播消息($emit):', () => { wrapper.vm.$bus.$on('foo', (payload) => { expect(payload).to.equal('$bus emitted an foo event'); }); wrapper.vm.$bus.$on('bar', (payload) => { expect(payload).to.equal('$bus emitted an bar event'); }); expect(Object.keys(vm.$bus._events).length).to.equal(2); wrapper.vm.$bus.$emit('foo', '$bus emitted an foo event'); wrapper.vm.$bus.$emit('bar', '$bus emitted an bar event'); }); });