新的一年(噗,都快年中了)
之前因為上家公司的經營出了問題,年前的大裁員,過了一個漫長的春節。 之後加入了新公司,然後正好趕上一個很緊急的專案,忙成狗,因此好久沒更新文章了。 不過,我又回來啦!
原文
前言
自動化測試,我們將使用karma和nightmare,內容會包括:
- 單元測試
- e2e測試(放下一篇文章) 其實,單元測試一般用在寫公共包的時候,比如通用的js函式庫,通用的UI元件庫。基本不太會在做業務專案的時候還使用單元測試。 然後,e2e測試的話,那其實往往是測試工程師需要做的,往往使用selenium。 那難道前端就不需要學測試了嗎? 答案肯定是否定的,不然我寫個毛...... vue的專案就用了單元測試和e2e。 基於vue的UI元件庫,比如:餓了麼的element、滴滴的cube-ui、有讚的vant等都有單元測試(咩有e2e,因為沒必要)。 滴滴的話,我問了下黃軼大佬,他們專案前端自動化測試是用了單元測試和e2e的。 總之,兩種都是由應用場景的,e2e雖然用的不多,或者說有時候不是前端自動化的範疇,但是其實做起來很簡單,學會準沒錯!
一、單元測試
安裝karma
npm install karma --save-dev
npm install karma-jasmine karma-chrome-launcher jasmine-core --save-dev
複製程式碼
在package.json
的scripts
配置
"test": "karma start"
初始化karma
$ karma init my.conf.js
Which testing framework do you want to use?
Press tab to list possible options. Enter to move to the next question.
> mocha
Do you want to use Require.js?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no
Do you want to capture a browser automatically?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome
>
What is the location of your source and test files?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Press Enter to move to the next question.
> test/**/*.js
>
Should any of the files included by the previous patterns be excluded?
You can use glob patterns, eg. "**/*.swp".
Press Enter to move to the next question.
>
Do you want Karma to watch all the files and run the tests on change?
Press tab to list possible options.
> yes
複製程式碼
初始成功以後,會生成一份karma.conf.js
配置檔案。
順便,在根目錄建立一個test
的資料夾,在這資料夾中建立一份index.js
,內容為:
describe('A spec suite', function() {
it('contains a passing spec', function() {
console.log('Hello Karma')
})
})
複製程式碼
執行一下:
npm run test
我們會看到程式會自動開啟chrome瀏覽器,然後顯示測試結果。
正式的寫個單元測試
重新組織test資料夾
.
└── unit
├── index.js
├── karma.conf.js
└── specs
└── dom.spec.js
複製程式碼
因為我們還要做e2e測試,所以,在test下,用各個資料夾區分,unit下就是單元測試的內容了。
安裝一系列包
karma-webpack
karma-sourcemap-loader
karma-coverage
chai
sinon
sinon-chai
karma-sinon-chai
karma-mocha-reporter
複製程式碼
karma-webpack
:因為我們的專案程式碼是用es6或者es7寫的,所以webpack編譯是必須的
karma-sourcemap-loader
:sourcemap明顯是必要的
karma-coverage
:做程式碼覆蓋率的時候需要用
chai
:搭配mocha斷言
sinon
:搭配mocha做spy、stub、mock
sinon-chai
:用chai來做sinon的斷言,可以說是擴充套件
karma-sinon-chai
:方便在測試程式碼中的呼叫,直接就能用expect、sinon.spy等,不需要每個檔案都import
karma-mocha-reporter
:mocha測試完的報告
像karma、mocha、chai、sinon這種測試工具,我不會很詳細的介紹,全部都介紹的話內容實在有點多,而且也比較枯燥。想要學習可以看我的參考資料,是我花了大把時間篩選整理出來的。
修改karma.conf.js
const webpackConfig = require('../../webpack.config.test.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'],
// list of files / patterns to load in the browser
files: [
'./index.js'
],
// 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: {
'./index.js': ['webpack', 'sourcemap', 'coverage']
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['mocha', 'coverage'],
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' }
]
},
.
.
.
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-only'
}
})
}
複製程式碼
karma原本在根目錄,我們直接移過來就好了。然後修改的不多,我稍微解釋一下:
- files:將要被測試的檔案
- preprocessors:在引入檔案前,需要用什麼方式處理,我們看到了,包括webpack、sourcemap、coverage
- reporters:測試完成後的報告,我們需要mocha的報告和coverage的報告
- coverageReporter:程式碼覆蓋率生成的報告檔案地址和存在形式設定
- webpack:在這需要引入webpack的配置,我們見到頂部,引入了webpack.test.config.js檔案,我們待會兒會介紹裡面的配置
- webpackMiddleware:
stats: 'errors-only'
我們讓webpack的編譯過程不顯示出來,除非編譯報錯
配置webpack.test.config.js
const webpackConfigBase = require('./webpack.config.base.js')
const config = Object.assign(webpackConfigBase.config, {
// sourcemap 模式
devtool: '#inline-source-map',
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
},
})
module.exports = config
``
#### 編輯index.js入口檔案
這個檔案是為了配合`karma-webpack`的,詳情見[Alternative Usage](https://github.com/webpack-contrib/karma-webpack#alternative-usage)
```js
// require all test files (files that ends with .spec.js)
// 語法說明:https://doc.webpack-china.org/guides/dependency-management/#require-context
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)
// require all src files which in the app/common/js for coverage.
// you can also change this to match only the subset of files that
// you want coverage for.
const srcContext = require.context('../../app/common/js/', true, /\.js$/)
srcContext.keys().forEach(srcContext)
複製程式碼
測試程式碼放在./specs
資料夾下,被測試原檔案在../../app/common/js/
下。這裡我只測試一些公共的js檔案,如果你需要測試其它,可自行修改。比如一些基於vue的UI元件庫,你想要測試所有元件程式碼,還需要做些配置上的修改,這方面可以參考滴滴的cube-ui專案,挺完整的,覆蓋率也很高。
正式寫測試程式碼
編輯dom.spec.js
檔案:
/**
* 測試common/utils/dom.js
*/
import * as dom from 'common/js/utils/dom.js'
// const expect = require('chai').expect 裝過sinon-chai就不需要這句了;sinon同理
describe('utils/dom', () => {
// 測試hasClass
it('hasClass', () => {
const ele = document.createElement('div')
ele.className = 'base kitty'
// 是否含有base
expect(dom.hasClass(ele, 'base')).to.be.equal(true)
// 是否含有kitty
expect(dom.hasClass(ele, 'kitty')).to.be.equal(true)
// 是否含有tom
expect(dom.hasClass(ele, 'tom')).to.be.equal(false)
// 無引數
expect(dom.hasClass()).to.be.equal(false)
})
// 測試addClass
it('addClass', () => {
const ele = document.createElement('div')
ele.className = 'base'
// 增加類名kitty
dom.addClass(ele, 'kitty')
expect(ele.className).to.be.equal('base kitty')
// 再增加類名kitty,希望並不會有重複類名
dom.addClass(ele, 'kitty')
expect(ele.className).to.be.equal('base kitty')
})
// 測試removeClass
it('removeClass', () => {
const ele = document.createElement('div')
ele.className = 'base kitty'
// 刪除類名kitty
dom.removeClass(ele, 'kitty')
expect(ele.className).to.be.equal('base')
// 刪除不存在的類名
dom.removeClass(ele, 'tom')
expect(ele.className).to.be.equal('base')
})
// 測試noce
it('once', () => {
const ele = document.createElement('div')
const callback = sinon.spy()
dom.once(ele, 'click', callback)
// 點選一次
ele.click()
expect(callback).to.have.been.calledOnce
// 再點選一次,預期應該是不呼叫callback的,所以依然為calledOnce
ele.click()
expect(callback).to.have.been.calledOnce
})
})
複製程式碼
程式碼註釋已經很清楚啦~
執行測試
先修改下package.json
配置:"test:unit": "karma start test/unit/karma.conf.js"
執行:
➜ construct git:(master) npm run test:unit
> vue-construct@1.0.0 test:unit /Users/Terry/WFE/vue-study/construct
> karma start test/unit/karma.conf.js
START:
ℹ 「wdm」:
ℹ 「wdm」: Compiled successfully.
ℹ 「wdm」: Compiling...
ℹ 「wdm」:
ℹ 「wdm」: Compiled successfully.
23 04 2018 01:25:39.438:INFO [karma]: Karma v2.0.0 server started at http://0.0.0.0:9876/
23 04 2018 01:25:39.440:INFO [launcher]: Launching browser Chrome with unlimited concurrency
23 04 2018 01:25:39.448:INFO [launcher]: Starting browser Chrome
23 04 2018 01:25:41.778:INFO [Chrome 66.0.3359 (Mac OS X 10.13.2)]: Connected on socket A9ZeKTNtnUU9MAceAAAA with id 51610088
utils/dom
✔ hasClass
✔ addClass
✔ removeClass
✔ once
Finished in 0.008 secs / 0.004 secs @ 01:25:41 GMT+0800 (CST)
SUMMARY:
✔ 4 tests completed
=============================== Coverage summary ===============================
Statements : 87.12% ( 142/163 ), 14 ignored
Branches : 61.25% ( 49/80 ), 22 ignored
Functions : 86.11% ( 31/36 ), 5 ignored
Lines : 90.79% ( 138/152 )
================================================================================
複製程式碼
參考資料
Sinon指南: 使用Mocks, Spies 和 Stubs編寫JavaScript測試
論文是個很有意思的東西,看多了你會驚人地發現,很多大廠有深度的文章其實都是對論文的純翻譯~ 另外還參考了vue和滴滴的cube-ui的專案測試部分。