摘要:單元測試的首要目的不是為了能夠編寫出大覆蓋率的全部通過的測試代碼,而是需要從使用者調用者的角度出發,嘗試函數邏輯的各種可能性,進而輔助性增強代碼質量測試是手段而不是目的。
本文已發布在稀土掘金
轉載請注明原文鏈接:https://github.com/ecmadao/Co...
雖然很多公司有自己的測試部門,而且前端開發大多不涉及測試環節,但鑒于目前前端領域的快速發展,其涉及面越來越廣,前端開發者們必然不能止步于目前的狀態。我覺得學會編寫良好的測試,不僅僅有利于自己整理需求、檢查代碼,更是一個優秀開發者的體現。
首先不得不推薦兩篇文章:
Intro前端自動化測試探索
測試驅動開發(TDD)介紹中的誤區
單元測試到底是什么?
需要訪問數據庫的測試不是單元測試
需要訪問網絡的測試不是單元測試
需要訪問文件系統的測試不是單元測試
--- 修改代碼的藝術
我們在單元測試中應該避免什么?
太多的條件邏輯
構造函數中做了太多事情
too many全局變量
too many靜態方法
無關邏輯
過多外部依賴
TDD(Test-driven development)測試驅動開發(TDD),其基本思路是通過測試來推動整個開發的進行。
單元測試的首要目的不是為了能夠編寫出大覆蓋率的全部通過的測試代碼,而是需要從使用者(調用者)的角度出發,嘗試函數邏輯的各種可能性,進而輔助性增強代碼質量
測試是手段而不是目的。測試的主要目的不是證明代碼正確,而是幫助發現錯誤,包括低級的錯誤
測試要快。快速運行、快速編寫
測試代碼保持簡潔
不會忽略失敗的測試。一旦團隊開始接受1個測試的構建失敗,那么他們漸漸地適應2、3、4或者更多的失敗。在這種情況下,測試集就不再起作用
IMPORTANT一定不能誤解了TDD的核心目的!
測試不是為了覆蓋率和正確率
而是作為實例,告訴開發人員要編寫什么代碼
紅燈(代碼還不完善,測試掛)-> 綠燈(編寫代碼,測試通過)-> 重構(優化代碼并保證測試通過)
大致過程需求分析,思考實現。考慮如何“使用”產品代碼,是一個實例方法還是一個類方法,是從構造函數傳參還是從方法調用傳參,方法的命名,返回值等。這時其實就是在做設計,而且設計以代碼來體現。此時測試為紅
實現代碼讓測試為綠
重構,然后重復測試
最終符合所有要求:
每個概念都被清晰的表達
Not Repeat Self
沒有多余的東西
通過測試
BDD(Behavior-driven development)行為驅動開發(BDD),重點是通過與利益相關者的討論,取得對預期的軟件行為的清醒認識,其重點在于溝通
大致過程
從業務的角度定義具體的,以及可衡量的目標
找到一種可以達到設定目標的、對業務最重要的那些功能的方法
然后像故事一樣描述出一個個具體可執行的行為。其描述方法基于一些通用詞匯,這些詞匯具有準確無誤的表達能力和一致的含義。例如,expect, should, assert
尋找合適語言及方法,對行為進行實現
測試人員檢驗產品運行結果是否符合預期行為。最大程度的交付出符合用戶期望的產品,避免表達不一致帶來的問題
測試的分類 & 測試工具 分類
API/Func UnitTest
測試不常變化的函數邏輯
測試前后端API接口
UI UnitTest
頁面自動截圖
頁面DOM元素檢查
跑通交互流程
工具Mocha + Chai
PhantomJS or CasperJS or Nightwatch.js
selenium
with python
with js
mocha + chai的API/Func UnitTestinitialmocha是一套前端測試工具,我們可以拿它和其他測試工具搭配。
而chai則是BDD/TDD測試斷言庫,提供諸如expect這樣的測試語法
下面兩篇文章值得一看:
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
測試的一個基本思路是,自身從函數的調用者出發,對函數進行各種情況的調用,查看其容錯程度、返回結果是否符合預期。
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", () => { // 對于多層嵌套的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無法自動監聽異步方法的完成,需要我們在完成之后手動調用done()方法
而如果要在回調之后使用異步測試語句,則需要使用try/catch進行捕獲。成功則done(),失敗則done(error)
// 普通的測試方法 it("should work", () =>{ console.log("Synchronous test"); }); // 異步的測試方法 it("should work", (done) =>{ setTimeout(() => { try { expect(1).not.to.equal(0); done(); // 成功 } catch (err) { done(err); // 失敗 } }, 200); });
異步測試有兩種方法完結:done或者返回Promise。而通過返回Promise,則不再需要編寫笨重的try/catch語句
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單元測試 Test React Componentmock是一個接口模擬庫,我們可以通過它來模擬代碼中的一些異步操作
React組件無法直接通過上述方法進行測試,需要安裝enzyme依賴。
$ npm i --save-dev enzyme # $ npm i --save-dev react-addons-test-utils
假設有這樣一個組件:
// ...省略部分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} 點我加1) } }
使用樣例:
import React from "react"; import {expect} from "chai"; import {shallow} from "enzyme"; import TestComponent from "../components/TestComponent"; describe("Test TestComponent", () => { // 創建一個虛擬的組件 const wrapper = shallow(Test Redux/ ); /* * 之后,我們可以: * 通過wrapper.state()拿到組件的state * 通過wrapper.instance()拿到組件實例,以此調用組件內的方法 * 通過wrapper.find()找到組件內的子組件 * 但是,無法通過wrapper.props()拿到組件的props */ // 測試該組件組外層的class it("should render with currect wrapper", () => { expect(wrapper.is(".test_component")).to.equal(true); }); // 測試該組件初始化的state it("should render with currect state", () => { expect(wrapper.state()).to.deep.equal({ clickNum: 10 }); }); // 測試組件的方法 it("should add one", () => { wrapper.instance().handleClick(); expect(wrapper.state()).to.deep.equal({ clickNum: 11 }); }); });
redux身為純函數,非常便于mocha進行測試
// 測試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); }); });
// 測試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()); // 一旦注冊就會時刻監聽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 }); // 注冊state監聽 let unsubscribe = subscribeListener(state, done); AppStore.dispatch(ACTIONS.addNewTodo("new todo")); // 結束之后取消監聽 unsubscribe(); }); });基于phantomjs和selenium的UI UnitTest
PhantomJS是一個基于webkit的服務器端JavaScript API,即相當于在內存中跑了個無界面的webkit內核的瀏覽器。通過它我們可以模擬頁面加載,并獲取到頁面上的DOM元素,進行一系列的操作,以此來模擬UI測試。但缺點是無法實時看見頁面上的情況(不過可以截圖)。
而Selenium是專門為Web應用程序編寫的一個驗收測試工具,它直接運行在瀏覽器中。Selenium測試通常會調起一個可見的界面,但也可以通過設置,讓它以PhantomJS的形式進行無界面的測試。
open 某個 url
監聽 onload 事件
事件完成后調用 sendEvent 之類的 api 去點擊某個 DOM 元素所在 point
觸發交互
根據 UI 交互情況 延時 setTimeout (規避惰加載組件點不到的情況)繼續 sendEvent 之類的交互
Getting started with Selenium Webdriver for node.js
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80389.html
摘要:單元測試過后,機器狀態保持不變。單元測試應該產生可重復一致的結果。然并卵都說國內很多程序員是不寫單元測試的,甚至從來都不寫,筆者當年做的時候也沒寫過幾次捂臉。回歸測試在單元測試的基礎上,我們就能夠建立關于這一模塊的回歸測試。 showImg(https://segmentfault.com/img/bVPMPd?w=463&h=312); 送給初級程序員的測試認知文 作為開發同學,一些...
摘要:單元測試是在軟件開發過程中要進行的最低級別的測試活動,軟件的獨立單元將在與程序的其他部分相隔離的情況下進行測試。隨機測試隨機測試是根據測試說明書執行用例測試的重要補充手段,是保證測試覆蓋完整性的有效方式和過程。 一、什么是軟件測試? ? ? ? ? 軟甲測試就是用來確認一個程序的品質或性能是...
摘要:之前談到過很多次數據驅動的理解,這次通過實際項目檢驗了一下自己的想法。對于數據驅動這種模式,至少從數據層,可以規避,做一層數據變化的效驗這個和寫服務端單側差不多。數據驅動和有點類似,只是借用在單頁面上實現了。 之前談到過很多次數據驅動的理解,這次通過實際項目檢驗了一下自己的想法。 相關文件 《前端數據驅動的價值》 《前端數據驅動的陷阱》 項目詳設 詳設的重要性 對于復雜一點的項目,...
摘要:本文主要關注的是接口測試。所謂接口測試,就是檢查系統提供的接口是否符合事先撰寫的接口文檔。作為接口測試的解決方案,我們必須具備通用性與易用性。 開始 最近幾年,前端測試漸漸被人重視,相關的框架和方法已經比較成熟。斷言庫有should, expect, chai。 單元測試框架有mocha, jasmine, Qunit。 模擬瀏覽器測試環境有Phantomjs, Slimerjs。 集...
摘要:本文主要關注的是接口測試。所謂接口測試,就是檢查系統提供的接口是否符合事先撰寫的接口文檔。作為接口測試的解決方案,我們必須具備通用性與易用性。 開始 最近幾年,前端測試漸漸被人重視,相關的框架和方法已經比較成熟。斷言庫有should, expect, chai。 單元測試框架有mocha, jasmine, Qunit。 模擬瀏覽器測試環境有Phantomjs, Slimerjs。 集...
閱讀 630·2021-09-24 09:48
閱讀 2492·2021-08-26 14:14
閱讀 518·2019-08-30 13:08
閱讀 1445·2019-08-29 15:22
閱讀 3067·2019-08-29 11:06
閱讀 1001·2019-08-26 18:26
閱讀 1036·2019-08-26 13:53
閱讀 2514·2019-08-26 12:21