1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        聊聊前端單元測(cè)試

        共 9618字,需瀏覽 20分鐘

         ·

        2021-03-21 18:31


        點(diǎn)上方藍(lán)字關(guān)注公眾號(hào)「程序員成長(zhǎng)指北

        作者:大笑

        來(lái)源:https://zhuanlan.zhihu.com/p/55887740


        先來(lái)幾個(gè)專業(yè)詞匯,這樣顯得高大上一點(diǎn)(不存在的=。=)

        BDD: Behavior-Driven Development (行為驅(qū)動(dòng)開(kāi)發(fā))
        TDD: Test-Driven Development (測(cè)試驅(qū)動(dòng)開(kāi)發(fā))
        ATDD: Acceptance Test Driven Development(驗(yàn)收測(cè)試驅(qū)動(dòng)開(kāi)發(fā))

        好,說(shuō)完了,然后我們廢話不多說(shuō),直接進(jìn)入正題。我會(huì)從多個(gè)測(cè)試框架入手,結(jié)合各種斷言庫(kù),用代碼方式說(shuō)明。


        單元測(cè)試(Unit Testing),是指對(duì)軟件中的最小可測(cè)試單元進(jìn)行檢查和驗(yàn)證。

        當(dāng)今所有著名的框架都要進(jìn)行單元測(cè)試,經(jīng)過(guò)測(cè)試的框架,它的信任度顯然高于未測(cè)試的框架。

        這里,我們介紹一下karma這個(gè)前端的單元測(cè)試框架。

        Spectacular Test Runner for Javascriptkarma-runner.github.io


        首先我們來(lái)安裝一波:
        新建一個(gè)文件夾,然后在空文件夾中打開(kāi)終端輸入

        npm init -y
        (sudo) npm install karma-cli -g
        npm install karma karma-jasmine karma-chrome-launcher jasmine-core --save-dev
        npm install karma-phantomjs-launcher --save-dev


        你安裝karma-cli這個(gè)倒是說(shuō)得過(guò)去,可是這個(gè)jasmine是啥,這個(gè)chrome-launcherphantomjs-launcher又是啥?

        沒(méi)錯(cuò),單說(shuō)測(cè)試框架是不完整的,必須要有斷言庫(kù)與之相配合,這里的jasmine就是斷言庫(kù)。

        啥是斷言(assert)?

        根據(jù)概念:

        斷言是編程術(shù)語(yǔ),表示為一些布爾表達(dá)式,程序員相信在程序中的某個(gè)特定點(diǎn)該表達(dá)式值為真,可以在任何時(shí)候啟用和禁用斷言驗(yàn)證,因此可以在測(cè)試時(shí)啟用斷言而在部署時(shí)禁用斷言。

        一言以蔽之,老子/老娘說(shuō)啥就是啥!聽(tīng)起來(lái)好像挺霸道的。那么具體呢?

        順著karma的正常流程向下走,我們來(lái)寫(xiě)一個(gè)簡(jiǎn)單的單元測(cè)試。在終端輸入:

        karma init


        你會(huì)發(fā)現(xiàn),需要做一個(gè)調(diào)查問(wèn)卷了,問(wèn)題如下:

        > 請(qǐng)問(wèn)你要用哪種測(cè)試框架呢?
        > 按tab鍵選擇,按回車(chē)鍵進(jìn)入下一個(gè)問(wèn)題。
        > jasmine
        (因?yàn)槲覀儼惭b的是jasmine,選什么斷言庫(kù)都別忘了安裝一下)

        > 您想要使用Require.js么?
        > 選擇yes的話,會(huì)安裝Require.js插件。
        > 按tab鍵選擇,按回車(chē)鍵進(jìn)入下一個(gè)問(wèn)題。
        > no
        (這里我們選擇no)

        > 你想要在什么瀏覽器中測(cè)試呢?
        > 按tab鍵選擇,輸入空字符串進(jìn)入下一個(gè)問(wèn)題。
        > Chrome
        > PhantomJS
        >

        注:上面的選擇這兩個(gè)瀏覽器的原因是我們之前安裝了這兩個(gè)瀏覽器的啟動(dòng)器(launcher)

        > 需要測(cè)試的源文件和測(cè)試命令文件放在哪呢?
        你可以使用通配符(glob patterns)來(lái)匹配文件,比如:"js/*.js" 或 "test/**/*Spec.js"
        輸入空字符串進(jìn)入下一個(gè)問(wèn)題。
        >
        (這里先留空,可根據(jù)測(cè)試情況靈活配置)

        >在符合匹配的文件中有哪些文件可以排除在外呢?
        你可以使用通配符來(lái)匹配文件,比如:"**/*.swp"
        輸入空字符串進(jìn)入下一個(gè)問(wèn)題。
        >

        > 你想要Karma根據(jù)文件的變化立即做出響應(yīng)么?
        > yes

        之后,你就會(huì)發(fā)現(xiàn)你的文件夾里多了一個(gè)文件:

        打開(kāi)這個(gè)文件,你會(huì)發(fā)現(xiàn)里面是一個(gè)配置項(xiàng)函數(shù):

        module.exports = function(config) {
        basePath: '', // 根路徑將會(huì)同files和excluede項(xiàng)中的相對(duì)路徑相關(guān)聯(lián)
        frameworks: ['jasmine'], // 所使用的測(cè)試框架
        files: [], // 這里是需要測(cè)試的文件列表,有多種配置方式
        exclude: [], // 測(cè)試過(guò)程中排除在外的文件列表
        reporters: ['progress'], // 測(cè)試結(jié)果的匯報(bào)方式,
        port: 9876, // web服務(wù)器接口
        colors: true, // 是否使用彩色報(bào)告
        logLevel: config.LOG_INFO, // 日志級(jí)別,可配置的值有: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        autoWatch: true, // 是否自動(dòng)觀測(cè)文檔改變并執(zhí)行測(cè)試命令
        browsers: ["Chrome", "PhantomJS"], // 用哪些瀏覽器測(cè)試呢
        singleRun: false, // 持續(xù)集成模式,如果設(shè)置成true,Karma將自行捕獲瀏覽器,運(yùn)行測(cè)試并根據(jù)結(jié)果退出,
        concurrency: Infinity // 并發(fā)數(shù),同時(shí)跑多少個(gè)瀏覽器進(jìn)行測(cè)試,默認(rèn)無(wú)上限
        }


        默認(rèn)會(huì)生成的配置項(xiàng)就是上面這些,更完整的配置請(qǐng)點(diǎn)我
        這里稍微提一下browsers配置項(xiàng),它可以配置高達(dá)8種瀏覽器:

        每一種都需要安裝對(duì)應(yīng)的launcher。其中有兩個(gè)需要注意chromeHeadlessPhantomJS。這兩個(gè)是無(wú)頭瀏覽器。所謂無(wú)頭瀏覽器就是沒(méi)有腦袋的瀏覽器。


        無(wú)頭瀏覽器即headless browser,是一種沒(méi)有界面的瀏覽器。既然是瀏覽器那么瀏覽器該有的東西它都應(yīng)該有,只是看不到界面而已。因此這種瀏覽器沒(méi)有渲染UI的過(guò)程,用于測(cè)試時(shí)的速度很快。


        這就回答了上文launcher是啥的問(wèn)題。畢竟,沒(méi)有瀏覽器靠腦補(bǔ)可沒(méi)法測(cè)試啊(真實(shí))

        言歸正傳。我們回到karma測(cè)試本身。接下來(lái),我們修改一下配置:

        files: ["src/srcTest/**/*.js", "test/unit/**/*.js"]


        注意,上述寫(xiě)法只是配置寫(xiě)法中的一種, 配置的文件位置也是隨您自己指定,更詳細(xì)的配置請(qǐng)點(diǎn)我

        采用上文寫(xiě)法的話,我們?cè)趂iles數(shù)組里面配置的第一項(xiàng)是需要測(cè)試的文件,第二項(xiàng)就是用什么方法去測(cè)試它的文件。

        因此,我們也在文件里創(chuàng)建對(duì)應(yīng)的文件夾:

        這里有一個(gè)要注意的點(diǎn)。我們的需要測(cè)試的文件和測(cè)試驅(qū)動(dòng)文件的名字是一一對(duì)應(yīng)的,區(qū)別就在于測(cè)試驅(qū)動(dòng)文件的名字后要加上.spec

        那么我們就在srcTest的文件里面寫(xiě)點(diǎn)什么吧....

        newBee.js

        // 減法函數(shù)
        function minus(x) {
        return function(y) {
        return x - y;
        };
        }


        testKarma.js

        // 加法函數(shù)
        function add(x) {
        return function(y) {
        return x + y;
        };
        }

        // 乘法函數(shù)
        function multi(x) {
        return function(y) {
        return x * y;
        };
        }

        //if函數(shù)測(cè)試
        function ifTest(boolean) {
        if (boolean) {
        return "熱熱";
        } else {
        return "涼涼";
        }
        }

        // 反轉(zhuǎn)字符串
        function reverseStr (string) {
        return string.split("").reverse().join("");
        }

        那么接下來(lái),就在.spec文件里寫(xiě)入對(duì)應(yīng)的測(cè)試斷言。我滴個(gè)龜龜,終于說(shuō)到斷言了。

        因?yàn)槲覀冞@里使用的是Jasmine,因此就先放一下它的官網(wǎng)。

        Globaljasmine.github.io


        我們結(jié)合實(shí)例來(lái)說(shuō)文檔

        newBee.spec.js

        describe("newBee單元測(cè)試", function() {
        it("減法函數(shù)測(cè)試", function() {
        var minus7 = minus(7);
        expect(minus7(6)).toBe(0);
        });
        });


        testKarma.spec.js

        describe("testKarma單元測(cè)試", function() {

        it("如果函數(shù)測(cè)試", function() {
        expect(ifTest(true)).toBe(true);
        expect(ifTest(false)).toBe("涼涼");
        });

        it("回文函數(shù)測(cè)試", function() {
        expect(reverseStr('abc')).toEqual('cba');
        })

        });


        基本的格式就是這樣的,下面來(lái)解釋一下

        // 分組describe(), 這個(gè)是可以嵌套的,并且每個(gè)單獨(dú)的測(cè)試都有beforeAll, afterAll, beforeEach和afterEach
        describe("這里寫(xiě)測(cè)試群組的名稱", function(){
        // 具體的測(cè)試,it(), 當(dāng)其中所有的斷言都為true時(shí),則通過(guò);否則失效。
        it('這里寫(xiě)具體測(cè)試的名稱', function(){
        var a = true;
        // 期望, expect()。匹配,to*()
        // 每個(gè)匹配方法在期望值和實(shí)際值之間執(zhí)行邏輯比較
        // 它負(fù)責(zé)告訴jasmine斷言的真假,從而決定測(cè)試的成功或失敗
        // 木有錯(cuò),老子/老娘說(shuō)啥就是啥
        expect(a).toBe(true); // 這是肯定斷言
        expect(!a).not.toBe(true); // 這是否定斷言

        // jasmine內(nèi)置的匹配方法有很多,亦可自定義匹配方法
        // toBe()
        // toEqual()
        // toMatch()
        // toBeUndefined()
        // toBeNull()
        // toBeTruthy()
        // toContain()
        // toBeLessThan()
        // toBeCloseTo()
        // toThrowError()
        // 等等等等
        })
        })


        那么,測(cè)試方法寫(xiě)完了,我們來(lái)實(shí)際運(yùn)行一下測(cè)試吧。打開(kāi)終端,輸入:

        karma start


        就會(huì)在終端看到

        可以看到,我們的測(cè)試在Chrome和PhantomJS瀏覽器中分別測(cè)試了的5個(gè)方法,都有2個(gè)沒(méi)有通過(guò)測(cè)試,沒(méi)錯(cuò),我們當(dāng)初在寫(xiě)測(cè)試的時(shí)候故意寫(xiě)錯(cuò)了(這是真的)。

        那么我們把測(cè)試修改成真值。

        newBee.spec.js

        describe("newBee單元測(cè)試", function() {
        it("減法函數(shù)測(cè)試", function() {
        var minus7 = minus(7);
        expect(minus7(6)).toBe(1);
        });
        });


        testKarma.spec.js

        it("如果函數(shù)測(cè)試", function() {
        expect(ifTest(true)).toBe("熱熱");
        expect(ifTest(false)).toBe("涼涼");
        });


        結(jié)果是:

        全部SUCCESS, 撒花。

        到這里,一個(gè)基本的測(cè)試流程就走完了。然而,這并非終點(diǎn)。

        其實(shí),還能更進(jìn)一步的。我們打開(kāi)終端:

        npm install karma-coverage --save-dev


        然后打開(kāi)karma.conf.js, 添加一些配置項(xiàng)

        // 這里配置哪些文件需要統(tǒng)計(jì)測(cè)試覆蓋率,例如,如果你的所有代碼文件都在src文件夾中,你就需要如下配置
        preprocessors: {
        "src/srcTest/*.js": "coverage"
        },
        // 新增coverageReporter選項(xiàng)
        // 配置覆蓋率報(bào)告的查看方式,type查看類型,可以取值html、text等等,dir輸出目錄
        coverageReporter: {
        dir: "docs/unit",
        reporters: [
        {
        type: "html",
        subdir: "report-html"
        }
        ]
        },
        reporters: ['progress', "coverage"] // 沒(méi)錯(cuò),reporters里面新增了一個(gè)coverage


        然后保存,再運(yùn)行一次karma start

        接著會(huì)發(fā)現(xiàn)你的項(xiàng)目里多了一個(gè)文件夾

        用瀏覽器打開(kāi)index.html。就會(huì)看到

        這就是你所寫(xiě)的js的測(cè)試覆蓋率。

        這樣看起來(lái)是不是高大上了一些呢?

        這里就有一個(gè)問(wèn)題了。普通的js可以測(cè)試,可是我是寫(xiě)Vue的啊,Vue組件怎么測(cè)試呢?很簡(jiǎn)單,Vue官網(wǎng)有非常詳細(xì)的測(cè)試教程。甚至還有專用的測(cè)試工具和測(cè)試說(shuō)明

        彳亍口巴,你說(shuō)的這些個(gè)單元測(cè)試看起來(lái)花里胡哨的,實(shí)際作用是什么呢?

        單元測(cè)試的好處

        1. 單元測(cè)試不但會(huì)使你的工作完成得更輕松。而且會(huì)令你的設(shè)計(jì)會(huì)變得更好,甚至大大減少你花在調(diào)試上面的時(shí)間。

        2. 提高代碼質(zhì)量

        3. 減少bug, 快速定位bug

        4. 使修改和重構(gòu)可以更放心

        5. 顯得專業(yè)

        單元測(cè)試的缺點(diǎn)

        開(kāi)發(fā)人員要花費(fèi)時(shí)間在寫(xiě)測(cè)試代碼上,然而又不會(huì)給你加工資...
        小項(xiàng)目寫(xiě)測(cè)試只能單純的增加開(kāi)發(fā)時(shí)間和成本,然而又不會(huì)給你加工資...
        我寫(xiě)了測(cè)試除了懂測(cè)試的人能看懂,別人又不知道,然而還不會(huì)給你加工資...


        別別別,別打我...你先聽(tīng)我道(hu)理(jiao)講(man)完(chan)。

        1. 對(duì)于所編寫(xiě)的代碼,你在調(diào)試上面畫(huà)了多少時(shí)間?

        2. 對(duì)于以前你自認(rèn)為正確的代碼,而實(shí)際上這些代碼卻存在重大的bug,你花了多少時(shí)間在重新確認(rèn)這些代碼上面?

        3. 對(duì)于一個(gè)別人報(bào)告的bug,你花了多少時(shí)間才找出導(dǎo)致這個(gè)bug的源碼位置?

        對(duì)于那些沒(méi)有使用單元測(cè)試的程序員而言,上面這些問(wèn)題所耗費(fèi)的時(shí)間的是逐漸增加的,而且項(xiàng)目越深入,花費(fèi)的時(shí)間越多;另一方面,適當(dāng)?shù)膯卧獪y(cè)試卻可以很大程度地減少這些時(shí)間,從而為你騰出足夠的時(shí)間來(lái)編寫(xiě)所有的單元測(cè)試——甚至可能還有剩余的空閑時(shí)間。

        更加真實(shí)的是,主流的框架必須要寫(xiě)測(cè)試

        不想當(dāng)程序員的設(shè)計(jì)師不是好運(yùn)維。----魯迅


        作為一個(gè)程序員,如果你想要讓自己寫(xiě)的框架放到github和npm上能夠?yàn)槭澜缟系钠渌怂?。那么一個(gè)最基本的前提就是————代碼沒(méi)有BUG??墒?,你的怎么向語(yǔ)言不通思維不同的人解釋你的JavaScript庫(kù)確實(shí)足夠健壯呢。這個(gè)時(shí)候就需要單元測(cè)試出場(chǎng)了。

        主流前端框架雖然在所使用的測(cè)試庫(kù)(karma、jest、QUnit)和斷言庫(kù)(assert、jasmine、 chai)上略有差別,但Vue、React、Angular、Underscore甚至是jQuery都寫(xiě)了單元測(cè)試。

        來(lái)個(gè)石錘

        下面我們看一看Vue的測(cè)試是怎么寫(xiě)的:

        git clone https://github.com/vuejs/vue.git
        npm install
        npm run test unit // 這里可以看到單元測(cè)試
        npm run test // 這里就看全部的測(cè)試


        Vue的測(cè)試覆蓋率為

        舉例:v-show的測(cè)試

        // import Vue from 'vue'

        describe('Directive v-show', () => {
        it('should check show value is truthy', () => {
        const vm = new Vue({
        template: '<div><span v-show="foo">hello</span></div>',
        data: { foo: true }
        }).$mount()
        expect(vm.$el.firstChild.style.display).toBe('')
        })

        it('should check show value is falsy', () => {
        const vm = new Vue({
        template: '<div><span v-show="foo">hello</span></div>',
        data: { foo: false }
        }).$mount()
        expect(vm.$el.firstChild.style.display).toBe('none')
        })

        it('should update show value changed', done => {
        const vm = new Vue({
        template: '<div><span v-show="foo">hello</span></div>',
        data: { foo: true }
        }).$mount()
        expect(vm.$el.firstChild.style.display).toBe('')
        vm.foo = false
        waitForUpdate(() => {
        expect(vm.$el.firstChild.style.display).toBe('none')
        vm.foo = {}
        }).then(() => {
        expect(vm.$el.firstChild.style.display).toBe('')
        vm.foo = 0
        }).then(() => {
        expect(vm.$el.firstChild.style.display).toBe('none')
        vm.foo = []
        }).then(() => {
        expect(vm.$el.firstChild.style.display).toBe('')
        vm.foo = null
        }).then(() => {
        expect(vm.$el.firstChild.style.display).toBe('none')
        vm.foo = '0'
        }).then(() => {
        expect(vm.$el.firstChild.style.display).toBe('')
        vm.foo = undefined
        }).then(() => {
        expect(vm.$el.firstChild.style.display).toBe('none')
        vm.foo = 1
        }).then(() => {
        expect(vm.$el.firstChild.style.display).toBe('')
        }).then(done)
        })

        it('should respect display value in style attribute', done => {
        const vm = new Vue({
        template: '<div><span v-show="foo" style="display:block">hello</span></div>',
        data: { foo: true }
        }).$mount()
        expect(vm.$el.firstChild.style.display).toBe('block')
        vm.foo = false
        waitForUpdate(() => {
        expect(vm.$el.firstChild.style.display).toBe('none')
        vm.foo = true
        }).then(() => {
        expect(vm.$el.firstChild.style.display).toBe('block')
        }).then(done)
        })

        it('should support unbind when reused', done => {
        const vm = new Vue({
        template:
        '<div v-if="tester"><span v-show="false"></span></div>' +
        '<div v-else><span @click="tester=!tester">show</span></div>',
        data: { tester: true }
        }).$mount()
        expect(vm.$el.firstChild.style.display).toBe('none')
        vm.tester = false
        waitForUpdate(() => {
        expect(vm.$el.firstChild.style.display).toBe('')
        vm.tester = true
        }).then(() => {
        expect(vm.$el.firstChild.style.display).toBe('none')
        }).then(done)
        })
        })


        只要你的測(cè)試覆蓋率足夠高,你就可以在著名的GitHub裝逼網(wǎng)站Codecov搞一個(gè)覆蓋率標(biāo)簽了。就像下面這個(gè):

        怎么樣,這樣你所寫(xiě)的框架,是不是就逼格滿滿?

        所以你還在等什么,測(cè)不了吃虧,測(cè)不了上當(dāng),趕緊在自己的代碼中加入測(cè)試吧,~~只要998~~,代碼逼格帶回家!


        ??愛(ài)心三連擊

        1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊,在看是我創(chuàng)作的動(dòng)力。

        2.關(guān)注公眾號(hào)程序員成長(zhǎng)指北,回復(fù)「1」加入高級(jí)前端交流群!「在這里有好多 前端 開(kāi)發(fā)者,會(huì)討論 前端 Node 知識(shí),互相學(xué)習(xí)」!

        3.也可添加微信【ikoala520】,一起成長(zhǎng)。

        “在看轉(zhuǎn)發(fā)”是最大的支持

        瀏覽 43
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            少妇高潮一区二区三区喷水 | 在线一区二区三区四区 | 俺不色| 国产熟女高潮又粗又大 | 亚洲天堂在线观看一区 | jizz日韩 | 中国一级黄片操逼片 | 麻豆成人免费视频 | 国产精品嫩草69夜色69夜色 | 青青草午夜 |