摘要:?jiǎn)卧獪y(cè)試的首要目的不是為了能夠編寫(xiě)出大覆蓋率的全部通過(guò)的測(cè)試代碼,而是需要從使用者調(diào)用者的角度出發(fā),嘗試函數(shù)邏輯的各種可能性,進(jìn)而輔助性增強(qiáng)代碼質(zhì)量測(cè)試是手段而不是目的。
本文已發(fā)布在稀土掘金
轉(zhuǎn)載請(qǐng)注明原文鏈接:https://github.com/ecmadao/Co...
雖然很多公司有自己的測(cè)試部門(mén),而且前端開(kāi)發(fā)大多不涉及測(cè)試環(huán)節(jié),但鑒于目前前端領(lǐng)域的快速發(fā)展,其涉及面越來(lái)越廣,前端開(kāi)發(fā)者們必然不能止步于目前的狀態(tài)。我覺(jué)得學(xué)會(huì)編寫(xiě)良好的測(cè)試,不僅僅有利于自己整理需求、檢查代碼,更是一個(gè)優(yōu)秀開(kāi)發(fā)者的體現(xiàn)。
首先不得不推薦兩篇文章:
Intro前端自動(dòng)化測(cè)試探索
測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(TDD)介紹中的誤區(qū)
單元測(cè)試到底是什么?
需要訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)的測(cè)試不是單元測(cè)試
需要訪(fǎng)問(wèn)網(wǎng)絡(luò)的測(cè)試不是單元測(cè)試
需要訪(fǎng)問(wèn)文件系統(tǒng)的測(cè)試不是單元測(cè)試
--- 修改代碼的藝術(shù)
我們?cè)趩卧獪y(cè)試中應(yīng)該避免什么?
太多的條件邏輯
構(gòu)造函數(shù)中做了太多事情
too many全局變量
too many靜態(tài)方法
無(wú)關(guān)邏輯
過(guò)多外部依賴(lài)
TDD(Test-driven development)測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(TDD),其基本思路是通過(guò)測(cè)試來(lái)推動(dòng)整個(gè)開(kāi)發(fā)的進(jìn)行。
單元測(cè)試的首要目的不是為了能夠編寫(xiě)出大覆蓋率的全部通過(guò)的測(cè)試代碼,而是需要從使用者(調(diào)用者)的角度出發(fā),嘗試函數(shù)邏輯的各種可能性,進(jìn)而輔助性增強(qiáng)代碼質(zhì)量
測(cè)試是手段而不是目的。測(cè)試的主要目的不是證明代碼正確,而是幫助發(fā)現(xiàn)錯(cuò)誤,包括低級(jí)的錯(cuò)誤
測(cè)試要快??焖龠\(yùn)行、快速編寫(xiě)
測(cè)試代碼保持簡(jiǎn)潔
不會(huì)忽略失敗的測(cè)試。一旦團(tuán)隊(duì)開(kāi)始接受1個(gè)測(cè)試的構(gòu)建失敗,那么他們漸漸地適應(yīng)2、3、4或者更多的失敗。在這種情況下,測(cè)試集就不再起作用
IMPORTANT一定不能誤解了TDD的核心目的!
測(cè)試不是為了覆蓋率和正確率
而是作為實(shí)例,告訴開(kāi)發(fā)人員要編寫(xiě)什么代碼
紅燈(代碼還不完善,測(cè)試掛)-> 綠燈(編寫(xiě)代碼,測(cè)試通過(guò))-> 重構(gòu)(優(yōu)化代碼并保證測(cè)試通過(guò))
大致過(guò)程需求分析,思考實(shí)現(xiàn)。考慮如何“使用”產(chǎn)品代碼,是一個(gè)實(shí)例方法還是一個(gè)類(lèi)方法,是從構(gòu)造函數(shù)傳參還是從方法調(diào)用傳參,方法的命名,返回值等。這時(shí)其實(shí)就是在做設(shè)計(jì),而且設(shè)計(jì)以代碼來(lái)體現(xiàn)。此時(shí)測(cè)試為紅
實(shí)現(xiàn)代碼讓測(cè)試為綠
重構(gòu),然后重復(fù)測(cè)試
最終符合所有要求:
每個(gè)概念都被清晰的表達(dá)
Not Repeat Self
沒(méi)有多余的東西
通過(guò)測(cè)試
BDD(Behavior-driven development)行為驅(qū)動(dòng)開(kāi)發(fā)(BDD),重點(diǎn)是通過(guò)與利益相關(guān)者的討論,取得對(duì)預(yù)期的軟件行為的清醒認(rèn)識(shí),其重點(diǎn)在于溝通
大致過(guò)程
從業(yè)務(wù)的角度定義具體的,以及可衡量的目標(biāo)
找到一種可以達(dá)到設(shè)定目標(biāo)的、對(duì)業(yè)務(wù)最重要的那些功能的方法
然后像故事一樣描述出一個(gè)個(gè)具體可執(zhí)行的行為。其描述方法基于一些通用詞匯,這些詞匯具有準(zhǔn)確無(wú)誤的表達(dá)能力和一致的含義。例如,expect, should, assert
尋找合適語(yǔ)言及方法,對(duì)行為進(jìn)行實(shí)現(xiàn)
測(cè)試人員檢驗(yàn)產(chǎn)品運(yùn)行結(jié)果是否符合預(yù)期行為。最大程度的交付出符合用戶(hù)期望的產(chǎn)品,避免表達(dá)不一致帶來(lái)的問(wèn)題
測(cè)試的分類(lèi) & 測(cè)試工具 分類(lèi)
API/Func UnitTest
測(cè)試不常變化的函數(shù)邏輯
測(cè)試前后端API接口
UI UnitTest
頁(yè)面自動(dòng)截圖
頁(yè)面DOM元素檢查
跑通交互流程
工具Mocha + Chai
PhantomJS or CasperJS or Nightwatch.js
selenium
with python
with js
mocha + chai的API/Func UnitTestinitialmocha是一套前端測(cè)試工具,我們可以拿它和其他測(cè)試工具搭配。
而chai則是BDD/TDD測(cè)試斷言庫(kù),提供諸如expect這樣的測(cè)試語(yǔ)法
下面兩篇文章值得一看:
Testing in ES6 with Mocha and Babel 6
Using Babel
$ npm i mocha --save-dev $ npm i chai --save-dev
babel 6+
$ npm install --save-dev babel-register $ npm install babel-preset-es2015 --save-dev
// package.json { "scripts": { "test": "./node_modules/mocha/bin/mocha --compilers js:babel-register" }, "babel": { "presets": [ "es2015" ] } }
babel 5+
$ npm install --save-dev babel-core
// package.json { "scripts": { "test": "./node_modules/mocha/bin/mocha --compilers js:babel-core/register" } }
$ npm install --save coffee-script
{ "scripts": { "test": "./node_modules/mocha/bin/mocha --compilers coffee:coffee-script/register" } }
After done both...
{ "scripts": { "test": "./node_modules/mocha/bin/mocha --compilers js:babel-core/register,coffee:coffee-script/register" } }
# $ mocha $ npm t $ npm testchai
import chai from "chai"; const assert = chai.assert; const expect = chai.expect; const should = chai.should();
foo.should.be.a("string"); foo.should.equal("bar"); list.should.have.length(3); obj.should.have.property("name"); expect(foo).to.be.a("string"); expect(foo).to.equal("bar"); expect(list).to.have.length(3); expect(obj).to.have.property("flavors"); assert.typeOf(foo, "string"); assert.equal(foo, "bar"); assert.lengthOf(list, 3); assert.property(obj, "flavors");Test
測(cè)試的一個(gè)基本思路是,自身從函數(shù)的調(diào)用者出發(fā),對(duì)函數(shù)進(jìn)行各種情況的調(diào)用,查看其容錯(cuò)程度、返回結(jié)果是否符合預(yù)期。
import chai from "chai"; const assert = chai.assert; const expect = chai.expect; const should = chai.should(); describe("describe a test", () => { it("should return true", () => { let example = true; // expect expect(example).not.to.equal(false); expect(example).to.equal(true); // should example.should.equal(true); example.should.be.a(boolen); [1, 2].should.have.length(2); }); it("should check an object", () => { // 對(duì)于多層嵌套的Object而言.. let nestedObj = { a: { b: 1 } }; let nestedObjCopy = Object.assign({}, nestedObj); nestedObj.a.b = 2; // do a function to change nestedObjCopy.a.b expect(nestedObjCopy).to.deep.equal(nestedObj); expect(nestedObjCopy).to.have.property("a"); }); });AsynTest
Testing Asynchronous Code with MochaJS and ES7 async/await
mocha無(wú)法自動(dòng)監(jiān)聽(tīng)異步方法的完成,需要我們?cè)谕瓿芍笫謩?dòng)調(diào)用done()方法
而如果要在回調(diào)之后使用異步測(cè)試語(yǔ)句,則需要使用try/catch進(jìn)行捕獲。成功則done(),失敗則done(error)
// 普通的測(cè)試方法 it("should work", () =>{ console.log("Synchronous test"); }); // 異步的測(cè)試方法 it("should work", (done) =>{ setTimeout(() => { try { expect(1).not.to.equal(0); done(); // 成功 } catch (err) { done(err); // 失敗 } }, 200); });
異步測(cè)試有兩種方法完結(jié):done或者返回Promise。而通過(guò)返回Promise,則不再需要編寫(xiě)笨重的try/catch語(yǔ)句
it("Using a Promise that resolves successfully with wrong expectation!", function() { var testPromise = new Promise(function(resolve, reject) { setTimeout(function() { resolve("Hello World!"); }, 200); }); return testPromise.then(function(result){ expect(result).to.equal("Hello!"); }); });mock
React單元測(cè)試 Test React Componentmock是一個(gè)接口模擬庫(kù),我們可以通過(guò)它來(lái)模擬代碼中的一些異步操作
React組件無(wú)法直接通過(guò)上述方法進(jìn)行測(cè)試,需要安裝enzyme依賴(lài)。
$ npm i --save-dev enzyme # $ npm i --save-dev react-addons-test-utils
假設(shè)有這樣一個(gè)組件:
// ...省略部分import代碼 class TestComponent extends React.Component { constructor(props) { super(props); let {num} = props; this.state = { clickNum: num } this.handleClick = this.handleClick.bind(this) } handleClick() { let {clickNum} = this.state; this.setState({ clickNum: clickNum + 1 }); } render() { let {clickNum} = this.state; return ({clickNum} 點(diǎn)我加1) } }
使用樣例:
import React from "react"; import {expect} from "chai"; import {shallow} from "enzyme"; import TestComponent from "../components/TestComponent"; describe("Test TestComponent", () => { // 創(chuàng)建一個(gè)虛擬的組件 const wrapper = shallow(Test Redux/ ); /* * 之后,我們可以: * 通過(guò)wrapper.state()拿到組件的state * 通過(guò)wrapper.instance()拿到組件實(shí)例,以此調(diào)用組件內(nèi)的方法 * 通過(guò)wrapper.find()找到組件內(nèi)的子組件 * 但是,無(wú)法通過(guò)wrapper.props()拿到組件的props */ // 測(cè)試該組件組外層的class it("should render with currect wrapper", () => { expect(wrapper.is(".test_component")).to.equal(true); }); // 測(cè)試該組件初始化的state it("should render with currect state", () => { expect(wrapper.state()).to.deep.equal({ clickNum: 10 }); }); // 測(cè)試組件的方法 it("should add one", () => { wrapper.instance().handleClick(); expect(wrapper.state()).to.deep.equal({ clickNum: 11 }); }); });
redux身為純函數(shù),非常便于mocha進(jìn)行測(cè)試
// 測(cè)試actions import * as ACTIONS from "../redux/actions"; describe("test actions", () => { it("should return an action to create a todo", () => { let expectedAction = { type: ACTIONS.NEW_TODO, todo: "this is a new todo" }; expect(ACTIONS.addNewTodo("this is a new todo")).to.deep.equal(expectedAction); }); });
// 測(cè)試reducer import * as REDUCERS from "../redux/reducers"; import * as ACTIONS from "../redux/actions"; describe("todos", () => { let todos = []; it("should add a new todo", () => { todos.push({ todo: "new todo", complete: false }); expect(REDUCERS.todos(todos, { type: ACTIONS.NEW_TODO, todo: "new todo" })).to.deep.equal([ { todo: "new todo", complete: false } ]); }); });
// 還可以和store混用 import { createStore, applyMiddleware, combineReducers } from "redux"; import thunk from "redux-thunk"; import chai from "chai"; import thunkMiddleware from "redux-thunk"; import * as REDUCERS from "../redux/reducers"; import defaultState from "../redux/ConstValues"; import * as ACTIONS from "../redux/actions" const appReducers = combineReducers(REDUCERS); const AppStore = createStore(appReducers, defaultState, applyMiddleware(thunk)); let state = Object.assign({}, AppStore.getState()); // 一旦注冊(cè)就會(huì)時(shí)刻監(jiān)聽(tīng)state變化 const subscribeListener = (result, done) => { return AppStore.subscribe(() => { expect(AppStore.getState()).to.deep.equal(result); done(); }); }; describe("use store in unittest", () => { it("should create a todo", (done) => { // 首先取得我們的期望值 state.todos.append({ todo: "new todo", complete: false }); // 注冊(cè)state監(jiān)聽(tīng) let unsubscribe = subscribeListener(state, done); AppStore.dispatch(ACTIONS.addNewTodo("new todo")); // 結(jié)束之后取消監(jiān)聽(tīng) unsubscribe(); }); });基于phantomjs和selenium的UI UnitTest
PhantomJS是一個(gè)基于webkit的服務(wù)器端JavaScript API,即相當(dāng)于在內(nèi)存中跑了個(gè)無(wú)界面的webkit內(nèi)核的瀏覽器。通過(guò)它我們可以模擬頁(yè)面加載,并獲取到頁(yè)面上的DOM元素,進(jìn)行一系列的操作,以此來(lái)模擬UI測(cè)試。但缺點(diǎn)是無(wú)法實(shí)時(shí)看見(jiàn)頁(yè)面上的情況(不過(guò)可以截圖)。
而Selenium是專(zhuān)門(mén)為Web應(yīng)用程序編寫(xiě)的一個(gè)驗(yàn)收測(cè)試工具,它直接運(yùn)行在瀏覽器中。Selenium測(cè)試通常會(huì)調(diào)起一個(gè)可見(jiàn)的界面,但也可以通過(guò)設(shè)置,讓它以PhantomJS的形式進(jìn)行無(wú)界面的測(cè)試。
open 某個(gè) url
監(jiān)聽(tīng) onload 事件
事件完成后調(diào)用 sendEvent 之類(lèi)的 api 去點(diǎn)擊某個(gè) DOM 元素所在 point
觸發(fā)交互
根據(jù) UI 交互情況 延時(shí) setTimeout (規(guī)避惰加載組件點(diǎn)不到的情況)繼續(xù) sendEvent 之類(lèi)的交互
Getting started with Selenium Webdriver for node.js
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/80389.html
摘要:?jiǎn)卧獪y(cè)試過(guò)后,機(jī)器狀態(tài)保持不變。單元測(cè)試應(yīng)該產(chǎn)生可重復(fù)一致的結(jié)果。然并卵都說(shuō)國(guó)內(nèi)很多程序員是不寫(xiě)單元測(cè)試的,甚至從來(lái)都不寫(xiě),筆者當(dāng)年做的時(shí)候也沒(méi)寫(xiě)過(guò)幾次捂臉。回歸測(cè)試在單元測(cè)試的基礎(chǔ)上,我們就能夠建立關(guān)于這一模塊的回歸測(cè)試。 showImg(https://segmentfault.com/img/bVPMPd?w=463&h=312); 送給初級(jí)程序員的測(cè)試認(rèn)知文 作為開(kāi)發(fā)同學(xué),一些...
摘要:?jiǎn)卧獪y(cè)試是在軟件開(kāi)發(fā)過(guò)程中要進(jìn)行的最低級(jí)別的測(cè)試活動(dòng),軟件的獨(dú)立單元將在與程序的其他部分相隔離的情況下進(jìn)行測(cè)試。隨機(jī)測(cè)試隨機(jī)測(cè)試是根據(jù)測(cè)試說(shuō)明書(shū)執(zhí)行用例測(cè)試的重要補(bǔ)充手段,是保證測(cè)試覆蓋完整性的有效方式和過(guò)程。 一、什么是軟件測(cè)試? ? ? ? ? 軟甲測(cè)試就是用來(lái)確認(rèn)一個(gè)程序的品質(zhì)或性能是...
摘要:之前談到過(guò)很多次數(shù)據(jù)驅(qū)動(dòng)的理解,這次通過(guò)實(shí)際項(xiàng)目檢驗(yàn)了一下自己的想法。對(duì)于數(shù)據(jù)驅(qū)動(dòng)這種模式,至少?gòu)臄?shù)據(jù)層,可以規(guī)避,做一層數(shù)據(jù)變化的效驗(yàn)這個(gè)和寫(xiě)服務(wù)端單側(cè)差不多。數(shù)據(jù)驅(qū)動(dòng)和有點(diǎn)類(lèi)似,只是借用在單頁(yè)面上實(shí)現(xiàn)了。 之前談到過(guò)很多次數(shù)據(jù)驅(qū)動(dòng)的理解,這次通過(guò)實(shí)際項(xiàng)目檢驗(yàn)了一下自己的想法。 相關(guān)文件 《前端數(shù)據(jù)驅(qū)動(dòng)的價(jià)值》 《前端數(shù)據(jù)驅(qū)動(dòng)的陷阱》 項(xiàng)目詳設(shè) 詳設(shè)的重要性 對(duì)于復(fù)雜一點(diǎn)的項(xiàng)目,...
摘要:本文主要關(guān)注的是接口測(cè)試。所謂接口測(cè)試,就是檢查系統(tǒng)提供的接口是否符合事先撰寫(xiě)的接口文檔。作為接口測(cè)試的解決方案,我們必須具備通用性與易用性。 開(kāi)始 最近幾年,前端測(cè)試漸漸被人重視,相關(guān)的框架和方法已經(jīng)比較成熟。斷言庫(kù)有should, expect, chai。 單元測(cè)試框架有mocha, jasmine, Qunit。 模擬瀏覽器測(cè)試環(huán)境有Phantomjs, Slimerjs。 集...
摘要:本文主要關(guān)注的是接口測(cè)試。所謂接口測(cè)試,就是檢查系統(tǒng)提供的接口是否符合事先撰寫(xiě)的接口文檔。作為接口測(cè)試的解決方案,我們必須具備通用性與易用性。 開(kāi)始 最近幾年,前端測(cè)試漸漸被人重視,相關(guān)的框架和方法已經(jīng)比較成熟。斷言庫(kù)有should, expect, chai。 單元測(cè)試框架有mocha, jasmine, Qunit。 模擬瀏覽器測(cè)試環(huán)境有Phantomjs, Slimerjs。 集...
閱讀 718·2021-09-24 09:48
閱讀 2516·2021-08-26 14:14
閱讀 542·2019-08-30 13:08
閱讀 1475·2019-08-29 15:22
閱讀 3112·2019-08-29 11:06
閱讀 1029·2019-08-26 18:26
閱讀 1131·2019-08-26 13:53
閱讀 2605·2019-08-26 12:21