- {{news.collect ? "已收藏" : "收藏"}}
- 轉發
- 評論
- {{news.like ? "取消贊" : "贊"}}
摘要:在前端進階之路前端架構設計測試核心這邊文章中通過分析了傳統手工測試的局限性去引出了測試驅動開發的理念并介紹了一些測試工具這篇文章我將通過一個的項目去講解如何使用且結合官方推薦的去進行單元測試的實戰一安裝我為本教程寫一個示例庫您可以直接
在《前端進階之路: 前端架構設計(3) - 測試核心》這邊文章中, 通過分析了"傳統手工測試的局限性" 去引出了測試驅動開發的理念, 并介紹了一些測試工具. 這篇文章我將通過一個Vue的項目, 去講解如何使用mocha & karma, 且結合vue官方推薦的vue-test-utils去進行單元測試的實戰.
一. 安裝我為本教程寫一個示例庫, 您可以直接跳過所有安裝過程, 安裝依賴后運行該示例項目:
如果想一步步進行安裝, 也可以跟著下面的步驟進行操作:
(一) 使用腳手架初始化vue項目(使用webpack模板)//命令行中輸入(默認閱讀該文章的讀者已經安裝vue-cli和node環境) vue init webpack vueunittest
注意, 當詢問到這一步Pick a test runner(Use arrow keys)時, 請選擇使用Karma and Mocha
接下來的操作進入項目npm install安裝相關依賴后(該步驟可能更會出現PhantomJS這個瀏覽器安裝失敗的報錯, 不用理會, 因為 之后我們不使用這個瀏覽器), npm run build即可.
(二) 安裝Karma-chrome-launch接下來安裝karma-chrome-launcher, 在命令行中輸入
npm install karma-chrome-launcher --save-dev
然后在項目中找到test/unit/karma.conf.js文件, 將PhantomJS瀏覽器修改為Chrome不要問我為什么不使用PhantomJS, 因為經常莫名的錯誤, 改成Chrome就不會!!!)
//karma.conf.js var webpackConfig = require("../../build/webpack.test.conf") module.exports = function (config) { config.set({ //browsers: ["PhantomJS"], browsers: ["Chrome"], ... }) }(三) 安裝Vue-test-utils
安裝Vue.js 官方的單元測試實用工具庫, 在命令行輸入:
npm install --save-dev vue-test-utils(四) 執行npm run unit
當你完成以上兩步的時候, 你就可以在命令行執行npm run unit嘗鮮你的第一次單元測試了, Vue腳手架已經初始化了一個HelloWorld.spec.js的測試文件去測試HelloWrold.vue, 你可以在test/unit/specs/HelloWorld.spec.js下找到這個測試文件.(提示: 將來所有的測試文件, 都將放specs這個目錄下, 并以測試腳本名.spec.js結尾命名!)
在命令行輸入npm run unit, 當你看到下圖所示的一篇綠的時候, 說明你的單元測試通過了!
二. 測試工具的使用方法下面是一個Counter.vue文件, 我將以該文件為基礎講解項目中測試工具的使用方法.
//Counter.vue(一) Mocha框架 1. Mocha測試腳本的寫法Counter.vue
{{ count }}
Mocha的作用是運行測試腳本, 要對上面Counter.vue進行測試, 我們就要寫測試腳本, 通常測試腳本應該與Vue組件名相同, 后綴為spec.js. 比如, Counter.vue組件的測試腳本名字就應該為Counter.spec.js
//Counter.spec.js import Vue from "vue" import Counter from "@/components/Counter" describe("Counter.vue", () => { it("點擊按鈕后, count的值應該為1", () => { //獲取組件實例 const Constructor = Vue.extend(Counter); //掛載組件 const vm = new Constructor().$mount(); //獲取button const button = vm.$el.querySelector("button"); //新建點擊事件 const clickEvent = new window.Event("click"); //觸發點擊事件 button.dispatchEvent(clickEvent); //監聽點擊事件 vm._watcher.run(); // 斷言:count的值應該是數字1 expect(Number(vm.$el.querySelector(".num").textContent)).to.equal(1); }) })
上面這段代碼就是一個測試腳本.測試腳本應該包含一個或多個describe, 每個describe塊應該包括一個或多個it塊
describe塊稱為"測試套件"(test suite), 表示一組相關的測試. 它是一個函數, 第一個參數是測試套件的名稱(通常寫測試組件的名稱, 這里即為Counter.js), 第二個參數是一個實際執行的函數.
it塊稱為"測試用例"(test case), 表示一個多帶帶的測試, 是測試的最小單位. 它也是一個函數, 第一個參數是測試用例的名稱(通常描述你的斷言結果, 這里即為"點擊按鈕后, count的值應該為1"), 第二個參數是一個實際執行的函數.
2. Mocha進行異步測試我們在Counter.vue組件中添加一個按鈕, 并添加一個異步自增的方法為incrementByAsync, 該函數設置一個延時器, 1000ms后count自增1.
... ...
給測試腳本中新增一個測試用例, 也就是it()
it("count異步更新, count的值應該為1", (done) => { ///獲取組件實例 const Constructor = Vue.extend(Counter); //掛載組件 const vm = new Constructor().$mount(); //獲取button const button = vm.$el.querySelectorAll("button")[1]; //新建點擊事件 const clickEvent = new window.Event("click"); //觸發點擊事件 button.dispatchEvent(clickEvent); //監聽點擊事件 vm._watcher.run(); //1s后進行斷言 window.setTimeout(() => { // 斷言:count的值應該是數字1 expect(Number(vm.$el.querySelector(".num").textContent)).to.equal(1); done(); }, 1000); })
Mocha中的異步測試, 需要給it()內函數的參數中添加一個done, 并在異步執行完后必須調用done(), 如果不調用done(), 那么Mocha會在2000ms后報錯且本次單元測試測試失敗(mocha默認的異步測試超時上線為2000ms), 錯誤信息如下:
3. Mocha的測試鉤子如果大家對于vue的mounted(), created()鉤子能夠理解的話, 對Mocha的鉤子也很容易理解, Mocha在describe塊中提供了四個鉤子: before(), after(), beforeEach(), afterEach(). 它們會在以下時間執行
describe("鉤子說明", function() { before(function() { // 在本區塊的所有測試用例之前執行 }); after(function() { // 在本區塊的所有測試用例之后執行 }); beforeEach(function() { // 在本區塊的每個測試用例之前執行 }); afterEach(function() { // 在本區塊的每個測試用例之后執行 }); });
上述就是Mocha的基本使用介紹, 如果想了解Mocha的更多使用方法, 可以查看下面的文檔和一篇阮一峰的Mocha教程:
Mocha官方文檔 : https://mochajs.org/
Mocha官方文檔翻譯 : http://www.jianshu.com/p/9c78...
阮一峰 - 測試框架 Mocha 實例教程 : http://www.ruanyifeng.com/blo...
(二) Chai斷言庫上面的測試用例中, 以expect()方法開頭的就是斷言.
expect(Number(vm.$el.querySelector(".num").textContent)).to.equal(1);
所謂斷言, 就是判斷源碼的實際執行結果與預期結果是否一致, 如果不一致, 就會拋出錯誤. 上面的斷言的意思是指: 有.num這類名的節點的內容應該為數字1. 斷言庫庫有很多種, Mocha并不限制你需要使用哪一種斷言庫, Vue的腳手架提供的斷言庫是sino-chai, 是一個基于Chai的斷言庫, 并且我們指定使用的是它的expect斷言風格.
expect斷言風格的優點很接近于自然語言, 下面是一些例子
// 相等或不相等 expect(1 + 1).to.be.equal(2); expect(1 + 1).to.be.not.equal(3); // 布爾值為true expect("hello").to.be.ok; expect(false).to.not.be.ok; // typeof expect("test").to.be.a("string"); expect({ foo: "bar" }).to.be.an("object"); expect(foo).to.be.an.instanceof(Foo); // include expect([1,2,3]).to.include(2); expect("foobar").to.contain("foo"); expect({ foo: "bar", hello: "universe" }).to.include.keys("foo"); // empty expect([]).to.be.empty; expect("").to.be.empty; expect({}).to.be.empty; // match expect("foobar").to.match(/^foo/);
每一個it()所包裹的測試用例都應該有一句或多句斷言,上面只是介紹了一部分的斷言語法, 如果想要知道更多Chai的斷言語法, 請查看以下的官方文檔.
Chai官方文檔: http://chaijs.com/
Chai官方文檔翻譯: http://www.jianshu.com/p/f200...
(三) Vue-test-utils測試庫 1. 在測試腳本中引入vue-test-utils//Counter.spec.js import Vue from "vue" import Counter from "@/components/Counter" //引入vue-test-utils import {mount} from "vue-test-utils"2. 測試文本內容
下面我將在Counter.spec.js測試腳本中對Counter.vue中的文本內容進行測試, 大家可以直觀的感受一下使用了Vue-test-utils后對.vue單文件組件的測試變得多么簡單.
未使用vue-test-utils的測試用例:
it("未使用Vue-test-utils: 正確渲染h3的文字為Counter.vue", () => { const Constructor = Vue.extend(Counter); const vm = new Constructor().$mount(); const H3 = vm.$el.querySelector("h3").textContent; expect(H3).to.equal("Counter.vue"); })
使用了vue-test-utils的測試用例:
it("使用Vue-test-Utils: 正確渲染h3的文字為Counter.vue", () => { const wrapper = mount(Counter); expect(wrapper.find("h3").text()).to.equal("Counter.vue"); })
從上面的代碼可以看出, vue-test-utils工具將該測試用例的代碼量減少了一半, 如果是更復雜的測試用例, 那么代碼量的減少將更為突出. 它可以讓我們更專注于去寫文件的測試邏輯, 將獲取組件實例和掛載的繁瑣的操作交由vue-test-utils去完成.
3. vue-test-utils的常用APIfind(): 返回匹配選擇器的第一個DOM節點或Vue組件的wrapper, 可以使用任何有效的選擇器
text(): 返回wrapper的文本內容
html(): 返回wrapper DOM的HTML字符串
it("find()/text()/html()方法", () => { const wrapper = mount(Counter); const h3 = wrapper.find("h3"); expect(h3.text()).to.equal("Counter.vue"); expect(h3.html()).to.equal("Counter.vue
"); })
trigger(): 在該 wrapper DOM 節點上觸發一個事件。
it("trigger()方法", () => { const wrapper = mount(Counter); const buttonOfSync = wrapper.find(".sync-button"); buttonOfSync.trigger("click"); buttonOfSync.trigger("click"); const count = Number(wrapper.find(".num").text()); expect(count).to.equal(2); })
setData(): 設置data的屬性并強制更新
it("setData()方法",() => { const wrapper = mount(Counter); wrapper.setData({foo: "bar"}); expect(wrapper.vm.foo).to.equal("bar"); })
上面介紹了幾個vue-test-utils提供的方法, 如果想深入學習vue-test-utils, 請閱讀下面的官方文檔:
三. 項目說明vue-test-utils官方文檔: https://vue-test-utils.vuejs....
該項目模仿了一個簡單的微博, 在代碼倉庫下載后, 可直接通過npm run dev運行.
(一) 項目效果圖 (二) 項目中的交互邏輯和需求在文本框中輸入內容后點擊"發布"按鈕(1), 會新發布內容到微博列表中, 且個人頭像等下的微博數量(6)會增加1個
當文本框中無內容時, 不能發布空微博到微博列表, 且彈出提示框, 叫用戶輸入內容
當點擊"關注"(2), 個人頭像下關注的數量(5)會增加1個, 且按鈕內字體變成"取消關注"; 當點擊"取消關注"(2), 個人頭像下的數量(5)會減少1個, 且按鈕內字體變成"關注"
當點擊"收藏"(3)時, 我的收藏(7)會增加1個數量, 且按鈕內文字變成"已收藏"; 點擊"已收藏"(3)時, 我的收藏(7)會減少1個數量, 且按鈕內文字變成"收藏"
當點擊"贊"(4), 我的贊(8)會增加1個數量, 且按鈕內文字變成"取消贊"; 點擊"取消贊"(3)時, 我的贊(8)會減少1個數量, 且按鈕內文字變成"贊"
(三) 項目源碼//SinaWeibo.vue四. 項目單元測試腳本實戰 {{news.name}}{{news.resource}}{{news.content}}
- {{news.collect ? "已收藏" : "收藏"}}
- 轉發
- 評論
- {{news.like ? "取消贊" : "贊"}}
我們將以上文提到的"項目中的交互邏輯和需求"為基礎, 為SinaWeibo.vue編寫測試腳本, 下面我將展示測試用例編寫過程:
1.在文本框中輸入內容后點擊"發布"按鈕(1), 會新發布內容到微博列表中, 且個人頭像等下的微博數量(6)會增加1個
it("點擊發布按鈕,發布新內容&個人微博數量增加1個", () => { const wrapper = mount(SinaWeibo); const textArea = wrapper.find(".weibo-publish-wrapper textarea"); const buttonOfPublish = wrapper.find(".weibo-publish-wrapper button"); const lengthOfWeiboNews = wrapper.vm.weiboNews.length; const countOfMyWeibo = wrapper.vm.profileData[2].num; //設置textArea的綁定數據 wrapper.setData({newWeiboContent: { imgUrl: "../../static/image/profile.jpg", name: "Lee_tanghui", resource: "剛剛 來自 網頁版微博", content: "歡迎來到我的微博", images: [] }}); //觸發點擊事件 buttonOfPublish.trigger("click"); const lengthOfWeiboNewsAfterPublish = wrapper.vm.weiboNews.length; const countOfMyWeiboAfterPublish = wrapper.vm.profileData[2].num; //斷言: 發布新內容 expect(lengthOfWeiboNewsAfterPublish).to.equal(lengthOfWeiboNews + 1); //斷言: 個人微博數量增加1個 expect(countOfMyWeiboAfterPublish).to.equal(countOfMyWeibo + 1); })
測試結果:
2.當文本框中無內容時, 不能發布空微博到微博列表, 且彈出提示框, 叫用戶輸入內容
it("當文本框中無內容時, 不能發布空微博到微博列表, 且彈出提示框", () => { const wrapper = mount(SinaWeibo); const textArea = wrapper.find(".weibo-publish-wrapper textarea"); const buttonOfPublish = wrapper.find(".weibo-publish-wrapper button"); const lengthOfWeiboNews = wrapper.vm.weiboNews.length; const countOfMyWeibo = wrapper.vm.profileData[2].num; //設置textArea的綁定數據為空 wrapper.setData({newWeiboContent: { imgUrl: "../../static/image/profile.jpg", name: "Lee_tanghui", resource: "剛剛 來自 網頁版微博", content: "", images: [] }}); //觸發點擊事件 buttonOfPublish.trigger("click"); const lengthOfWeiboNewsAfterPublish = wrapper.vm.weiboNews.length; const countOfMyWeiboAfterPublish = wrapper.vm.profileData[2].num; //斷言: 沒有發布新內容 expect(lengthOfWeiboNewsAfterPublish).to.equal(lengthOfWeiboNews); //斷言: 個人微博數量不變 expect(countOfMyWeiboAfterPublish).to.equal(countOfMyWeibo); })
測試結果:
3.當點擊"關注"(2), 個人頭像下關注的數量(5)會增加1個, 且按鈕內字體變成"取消關注"; 當點擊"取消關注"(2), 個人頭像下的數量(5)會減少1個, 且按鈕內字體變成"關注"
it("當點擊"關注", 個人頭像下關注的數量會增加1個, 且按鈕內字體變成"取消關注"", () => { const wrapper = mount(SinaWeibo); const buttonOfAddAttendion = wrapper.find(".add"); const countOfMyAttention = wrapper.vm.profileData[0].num; //觸發事件 buttonOfAddAttendion.trigger("click"); const countOfMyAttentionAfterClick = wrapper.vm.profileData[0].num; //斷言: 個人頭像下關注的數量會增加1個 expect(countOfMyAttentionAfterClick).to.equal(countOfMyAttention + 1); //斷言: 按鈕內字體變成"取消關注 expect(buttonOfAddAttendion.text()).to.equal("取消關注"); })
it("當點擊"取消關注", 個人頭像下關注的數量會減少1個, 且按鈕內字體變成"關注"", () => { const wrapper = mount(SinaWeibo); const buttonOfUnAttendion = wrapper.find(".cancel"); const countOfMyAttention = wrapper.vm.profileData[0].num; //觸發事件 buttonOfUnAttendion.trigger("click"); const countOfMyAttentionAfterClick = wrapper.vm.profileData[0].num; //斷言: 個人頭像下關注的數量會增加1個 expect(countOfMyAttentionAfterClick).to.equal(countOfMyAttention - 1); //斷言: 按鈕內字體變成"取消關注 expect(buttonOfUnAttendion.text()).to.equal("關注"); })
測試結果:
4.當點擊"收藏"(3)時, 我的收藏(7)會增加1個數量, 且按鈕內文字變成"已收藏"; 點擊"已收藏"(3)時, 我的收藏(7)會減少1個數量, 且按鈕內文字變成"收藏"
it("當點擊"收藏"時, 我的收藏會增加1個數量, 且按鈕內文字變成"已收藏"", () => { const wrapper = mount(SinaWeibo); const buttonOfCollect = wrapper.find(".collectWeibo"); const countOfMyCollect = Number(wrapper.find(".collect-num span").text()); //觸發點擊事件 buttonOfCollect.trigger("click"); const countOfMyCollectAfterClick = Number(wrapper.find(".collect-num span").text()); //斷言: 我的收藏數量會加1 expect(countOfMyCollectAfterClick).to.equal(countOfMyCollect + 1); //斷言: 按鈕內文字變成已收藏 expect(buttonOfCollect.text()).to.equal("已收藏"); })
it("當點擊"已收藏"時, 我的收藏會減少1個數量, 且按鈕內文字變成"收藏"", () => { const wrapper = mount(SinaWeibo); const buttonOfUnCollect = wrapper.find(".uncollectWeibo"); const countOfMyCollect = Number(wrapper.find(".collect-num span").text()); //觸發點擊事件 buttonOfUnCollect.trigger("click"); const countOfMyCollectAfterClick = Number(wrapper.find(".collect-num span").text()); //斷言: 我的收藏數量會減1 expect(countOfMyCollectAfterClick).to.equal(countOfMyCollect - 1 ); //斷言: 按鈕內文字變成已收藏 expect(buttonOfUnCollect.text()).to.equal("收藏"); })
測試結果:
5.當點擊"贊"(4), 我的贊(8)會增加1個數量, 且按鈕內文字變成"取消贊"; 點擊"取消贊"(3)時, 我的贊(8)會減少1個數量, 且按鈕內文字變成"贊"
it("當點擊"贊", 我的贊會增加1個數量, 且按鈕內文字變成"取消贊"", () => { const wrapper = mount(SinaWeibo); const buttonOfLike = wrapper.find(".dislikedWeibo"); const countOfMyLike = Number(wrapper.find(".like-num span").text()); //觸發點擊事件 buttonOfLike.trigger("click"); const countOfMyLikeAfterClick = Number(wrapper.find(".like-num span").text()); //斷言: 我的贊會增加1個數量 expect(countOfMyLikeAfterClick).to.equal(countOfMyLike + 1); //斷言: 按鈕內文字變成取消贊 expect(buttonOfLike.text()).to.equal("取消贊"); });
it("當點擊"取消贊", 我的贊會減少1個數量, 且按鈕內文字變成"贊"", () => { const wrapper = mount(SinaWeibo); const buttonOfDislike = wrapper.find(".likedWeibo"); const countOfMyLike = Number(wrapper.find(".like-num span").text()); //觸發點擊事件 buttonOfDislike.trigger("click"); const countOfMyLikeAfterClick = Number(wrapper.find(".like-num span").text()); //斷言: 我的贊會增加1個數量 expect(countOfMyLikeAfterClick).to.equal(countOfMyLike - 1); //斷言: 按鈕內文字變成取消贊 expect(buttonOfDislike.text()).to.equal("贊"); });
測試結果
項目地址:Git倉庫: https://github.com/Lee-Tanghu...參考文章
測試框架 Mocha 實例教程 - 阮一峰
Chai.js斷言庫API中文文檔
知乎: 如果對vue進行單元測試
Vue.js學習系列六——Vue單元測試Karma+Mocha學習筆記
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/90554.html
摘要:但是,項目中的一些公共封裝,比如公共的組件公用的功能模塊等是可以使用單元測試的。因此特為組件庫引入單元測試,目的在于能減少組件的,避免重復的發布不必要的包。 項目github地址:https://github.com/yuanalina/installAsRequired這里必須要提前說明,前端項目的單元測試不是必須的,特別是業務型項目,增加單元測試反而會成為累贅,增加開發成本且無意義...
摘要:基礎知識作用為提供瀏覽器測試環境,為真正測試框架,為斷言庫測試用例基礎塊稱為測試套件,表示一組相關的測試。它也是一個函數,第一個參數是測試用例的名稱,第二個參數是一個實際執行的函數。 基礎知識 karma作用為提供瀏覽器測試環境,mocha為真正測試框架,chai為斷言庫 測試用例基礎 describe塊稱為測試套件(test suite),表示一組相關的測試。它是一個函數,第一個...
摘要:安裝安裝依賴庫安裝已經相關的插件,可以使用或者使用在這篇文章中,我使用和,如果你不喜歡這兩個庫,你可以選擇你喜歡的任何一個庫,只要它能在瀏覽器中運行就可以。 本文翻譯自:Automated testing with Headless Chrome作者:Eric Bidelman (Google 工程師)譯者:justjavac 如果您想使用 Headless Chrome 進行自動測試...
閱讀 3274·2021-11-15 11:37
閱讀 1088·2021-11-02 14:45
閱讀 3905·2021-09-04 16:48
閱讀 3582·2019-08-30 15:55
閱讀 758·2019-08-23 17:53
閱讀 1000·2019-08-23 17:03
閱讀 2035·2019-08-23 16:43
閱讀 2193·2019-08-23 16:22