<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          聊聊前端與單元測試

          共 9518字,需瀏覽 20分鐘

           ·

          2021-03-10 10:40

          點上方藍字關注公眾號「前端UpUp

          作者:大笑

          來源:https://zhuanlan.zhihu.com/p/55887740


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

          BDD: Behavior-Driven Development (行為驅(qū)動開發(fā))
          TDD: Test-Driven Development (測試驅(qū)動開發(fā))
          ATDD: Acceptance Test Driven Development(驗收測試驅(qū)動開發(fā))

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


          單元測試(Unit Testing),是指對軟件中的最小可測試單元進行檢查和驗證。

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

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

          Spectacular Test Runner for Javascriptkarma-runner.github.io


          首先我們來安裝一波:
          新建一個文件夾,然后在空文件夾中打開終端輸入

          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這個倒是說得過去,可是這個jasmine是啥,這個chrome-launcherphantomjs-launcher又是啥?

          沒錯,單說測試框架是不完整的,必須要有斷言庫與之相配合,這里的jasmine就是斷言庫。

          啥是斷言(assert)

          根據(jù)概念:

          斷言是編程術語,表示為一些布爾表達式,程序員相信在程序中的某個特定點該表達式值為真,可以在任何時候啟用和禁用斷言驗證,因此可以在測試時啟用斷言而在部署時禁用斷言。

          一言以蔽之,老子/老娘說啥就是啥!聽起來好像挺霸道的。那么具體呢?

          順著karma的正常流程向下走,我們來寫一個簡單的單元測試。在終端輸入:

          karma init


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

          > 請問你要用哪種測試框架呢?
          > 按tab鍵選擇,按回車鍵進入下一個問題。
          > jasmine
          (因為我們安裝的是jasmine,選什么斷言庫都別忘了安裝一下)

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

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

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

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

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

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

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

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

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


          默認會生成的配置項就是上面這些,更完整的配置請點我
          這里稍微提一下browsers配置項,它可以配置高達8種瀏覽器:

          每一種都需要安裝對應的launcher。其中有兩個需要注意chromeHeadlessPhantomJS。這兩個是無頭瀏覽器。所謂無頭瀏覽器就是沒有腦袋的瀏覽器


          無頭瀏覽器即headless browser,是一種沒有界面的瀏覽器。既然是瀏覽器那么瀏覽器該有的東西它都應該有,只是看不到界面而已。因此這種瀏覽器沒有渲染UI的過程,用于測試時的速度很快。


          這就回答了上文launcher是啥的問題。畢竟,沒有瀏覽器靠腦補可沒法測試啊(真實)

          言歸正傳。我們回到karma測試本身。接下來,我們修改一下配置:

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


          注意,上述寫法只是配置寫法中的一種, 配置的文件位置也是隨您自己指定,更詳細的配置請點我

          采用上文寫法的話,我們在files數(shù)組里面配置的第一項是需要測試的文件,第二項就是用什么方法去測試它的文件

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

          這里有一個要注意的點。我們的需要測試的文件和測試驅(qū)動文件的名字是一一對應的,區(qū)別就在于測試驅(qū)動文件的名字后要加上.spec

          那么我們就在srcTest的文件里面寫點什么吧....

          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ù)測試
          function ifTest(boolean) {
          if (boolean) {
          return "熱熱";
          } else {
          return "涼涼";
          }
          }

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

          那么接下來,就在.spec文件里寫入對應的測試斷言。我滴個龜龜,終于說到斷言了。

          因為我們這里使用的是Jasmine,因此就先放一下它的官網(wǎng)。

          Globaljasmine.github.io


          我們結(jié)合實例來說文檔

          newBee.spec.js

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


          testKarma.spec.js

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

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

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

          });


          基本的格式就是這樣的,下面來解釋一下

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

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


          那么,測試方法寫完了,我們來實際運行一下測試吧。打開終端,輸入:

          karma start


          就會在終端看到

          可以看到,我們的測試在Chrome和PhantomJS瀏覽器中分別測試了的5個方法,都有2個沒有通過測試,沒錯,我們當初在寫測試的時候故意寫錯了(這是真的)。

          那么我們把測試修改成真值。

          newBee.spec.js

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


          testKarma.spec.js

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


          結(jié)果是:

          全部SUCCESS, 撒花。

          到這里,一個基本的測試流程就走完了。然而,這并非終點。

          其實,還能更進一步的。我們打開終端:

          npm install karma-coverage --save-dev


          然后打開karma.conf.js, 添加一些配置項

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


          然后保存,再運行一次karma start

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

          用瀏覽器打開index.html。就會看到

          這就是你所寫的js的測試覆蓋率。

          這樣看起來是不是高大上了一些呢?

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

          彳亍口巴,你說的這些個單元測試看起來花里胡哨的,實際作用是什么呢?

          單元測試的好處

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

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

          3. 減少bug, 快速定位bug

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

          5. 顯得專業(yè)

          單元測試的缺點

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


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

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

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

          3. 對于一個別人報告的bug,你花了多少時間才找出導致這個bug的源碼位置?

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

          更加真實的是,主流的框架必須要寫測試

          不想當程序員的設計師不是好運維。----魯迅


          作為一個程序員,如果你想要讓自己寫的框架放到github和npm上能夠為世界上的其他人所用。那么一個最基本的前提就是————代碼沒有BUG。可是,你的怎么向語言不通思維不同的人解釋你的JavaScript庫確實足夠健壯呢。這個時候就需要單元測試出場了。

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

          來個石錘

          下面我們看一看Vue的測試是怎么寫的:

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


          Vue的測試覆蓋率為

          舉例:v-show的測試

          // 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)
          })
          })


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

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

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


           感謝大家

          1. 關注「前端UpUp」,分享精選面試熱點文章。

          2. 加我好友,一起討論算法,2021一起UpUp。

          瀏覽 43
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文字幕性爱电影 | 五月天婷婷av | 亚洲欧美视频 | 操美女屁股 | 国语对白在线视频 |