<font id="zqva1"></font>
<rt id="zqva1"></rt>
  • <tt id="zqva1"></tt>
    <cite id="zqva1"></cite>

    <cite id="zqva1"><noscript id="zqva1"></noscript></cite>
      <rp id="zqva1"><meter id="zqva1"></meter></rp>

        <cite id="zqva1"></cite>
          <b id="zqva1"></b>
          <rp id="zqva1"></rp>
          <cite id="zqva1"></cite>

          <rt id="zqva1"></rt>

        1. <rp id="zqva1"></rp>

          mocha自動化測試程序:node.js測試框架Mocha源碼分析

          時間:?2017-11-21閱讀:?2639標簽:?node

          一、分析目標

          分析前端單元測試工具 Mocha, 了解它是怎么進行TDD和BDD的。

          二、測試與Mocha入門簡介

          1. 測試的概念

          1.1 單元測試

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

          • 在一些傳統的結構化編程語言中,例如C,要進行測試的單元一般是函數或子過程。
          • 在面向對象的語言中,要進行測試基本單元是類。

          1.2 斷言

          斷言表示一些布爾表達式,在編寫代碼的時候,我們總是會作出一些假設,斷言就是用于代碼中捕捉這些假設。 在單元測試中,我們經常使用斷言來驗證我們的代碼是否正常運行。斷言可以有兩種形式

          assert Expresstion1
          assert Expresstion1:Expresstion2
          

          其中Expression1應該總是一個布爾值,Expression2是斷言失敗時,輸出的失敗消息的字符串。

          1.3 TDD

          測試驅動開發是敏捷開發中的一項核心實踐和技術,也是一種設計方法論。TDD的原理是在開發功能代碼之前,先編寫單元測試用例代碼,測試代碼確定需要編寫什么產品代碼。TDD的基本思路就是通過測試來推動整個開發的進行,但測試驅動開發并不只是單純的測試工作,而是把需求分析,設計,質量控制量化的過程。TDD首先考慮使用需求(對象、功能、過程、接口等),主要是編寫測試用例框架對功能的過程和接口進行設計,而測試框架可以持續進行驗證。

          1.4 BDD

          行為驅動開發是一種敏捷軟件開發的技術,它鼓勵軟件項目中的開發者、 QA和非技術人員或商業參與者之間的協作。主要是從用戶的需求出發,強調系統行為。BDD最初是由Dan North在2003年命名,它包括驗收測試和客戶測試驅動等的極限編程的實踐,作為對測試驅動開發的回應。

          2. mocha簡介

          Mocha.js是被廣泛使用的Javascript測試框架,在瀏覽器和Node環境都可以使用。Mocha提供TDD和BDD的測試接口。 Mocha提供了

          • 斷言單元測試,可以進行功能性測試
          • 同步代碼測試
          • 異步代碼測試

          2.1 斷言

          在Mocha中運行你使用任何斷言庫來進行代碼的測試,其中有 | 庫 | 描述 | | :-------------- | :-------------------------------------------------------- | | should.js | BDD風格的測試接口 | | expect.js | expect()風格的斷言 | | chai | 提供expect(),assert()和should這幾種風格的斷言 | | better-assert | C語言風格的斷言 | | unexpected | BDD斷言的擴展 | 當然,我們可以用Nodejs內建的assert模塊來進行斷言。

          describe("測試數組", function(){
              it("測試indexOf()", function(){
                  assert.equal(-1, [1,2,3].indexOf(4));
              });
          });
          

          2.2 TDD測試接口

          TDD測試風格的接口:

          • suite: 定義一組測試用例(也可以是一個,可以嵌套)
          • suiteSetup: 此方法會在這個suite所有測試用例執行前,執行有且只有一次。
          • setup: 此方法會在每個測試用例執行前都執行一遍。
          • test: 具體執行的測試用例實現代碼。
          • teardown: 此方法會在這個suite所有測試用例執行后都執行一次,與setup相反
          • suiteTeardown: 此方法會在這個suite所有測試用例執行后執行一次,與suiteTeardown相反。

          2.3 BDD測試接口

          BDD測試風格的接口

          • describe(): 描述場景,在里面可以設定Context,可包括多個測試用例,也可以嵌套場景
          • it(): 位于場景內,描述測試用例
          • before(): 所有測試用例的統一前置動作
          • after(): 所有測試用例的統一后置動作
          • beforeEach(): 每個測試用例的前置動作
          • aferEach(): 每個測試用例的后置動作

          2.4 Mocha支持的特性

          1. 異步測試 Mocha默認每個測試用例最多2000ms,如果到時沒有得到結果,就會報錯,對于涉及異步操作的測試用例,我們需要用-t或--timeout參數指定超時門檻。
          2. 測試用例管理 在大型項目中,會有很多的測試用例,我們可以通過幾個方法來進行管理
            • only: 表示只運行某個測試套件或測試用例
            • skip: 表示跳過指定的測試套件或測試用例

          三、Mocha的使用

          首先,mocha的默認模式是BDD,我們以BDD為例看一下mocha是怎么運行的: 這個是要測試的源文件,傳入兩個數字,返回和

          // add.js
          export default (a, b) => a + b;
          這個是測試的文件,用了chai這個斷言庫,expect(add(1, 1)).to.be.equal(2) 是斷言部分
          // test.js
          import add from "./add"
          import { expect } from "chai"
          
          describe('Test Start',() => {
              describe('加法函數測試',() => {
                  it('1 + 1 等于 2', () => expect(add(1, 1)).to.be.equal(2))
                  it('返回值是Number',() => expect(add(1, 1)).to.be.a('number'))
              })
          })
          

          因為用了ES6的語法,需要babel轉一下

          // index.js
          require("babel-register")({
              presets: [
                  "es2015",
                  "stage-0"
              ],
              plugins: ["transform-async-to-generator","transform-runtime"]
          });
          
          require('./test');
          

          命令行運行結果:


          四、分析實現

          1. Command-line interfaces

          首先,Mocha的命令行接口是用 commander 寫的,在/bin/_mocha文件中可以看到。默認ui是bdd,默認文件是test/*

          // bin/_mocha
          program
            ...
            .option('-u, --ui <name>', 'specify user-interface (' + interfaceNames.join('|') + ')', 'bdd')
          ...
          var args = program.args;
          // default files to test/*.{js,coffee}
          if (!args.length) {
            args.push('test');
          }
          args.forEach(function (arg) {
            var newFiles;
            try {
              newFiles = utils.lookupFiles(arg, extensions, program.recursive);
            } catch (err) {
              ...
            }
          
            files = files.concat(newFiles);
          });
          ...
          mocha.files = files;
          runner = mocha.run(program.exit ? exit : exitLater);
          ...
          

          2. Mocha run entrypoint

          // lib/mocha.js
          Mocha.prototype.run = function (fn) {
            if (this.files.length) {
              this.loadFiles();
            }
          
            ...
            return runner.run(done);
          };
          

          其中loadFiles是用來加載文件的,我們看一下他的源碼:

          // lib/mocha.j
          Mocha.prototype.loadFiles = function (fn) {
            var self = this;
            var suite = this.suite;
            this.files.forEach(function (file) {
              file = path.resolve(file);
              suite.emit('pre-require', global, file, self);
              suite.emit('require', require(file), file, self);
              suite.emit('post-require', global, file, self);
            });
            fn && fn();
          };
          

          這里通過emit觸發了pre-require事件,那么pre-require事件在哪呢?

          3. BDD Interfaces

          // lib/interfaces/bdd.js
          suite.on('pre-require', function (context, file, mocha) {
            var common = require('./common')(suites, context, mocha);
          
            context.before = common.before;
            context.after = common.after;
            context.beforeEach = common.beforeEach;
            context.afterEach = common.afterEach;
            context.run = mocha.options.delay && common.runWithSuite(suite);
            /**
             * Describe a "suite" with the given `title`
             * and callback `fn` containing nested suites
             * and/or tests.
             */
          
            context.describe = context.context = function (title, fn) {
              ...
            };
            ...
            context.it = context.specify = function (title, fn) {
              ...
            };
          });
          

          其中context是emit時候傳入的global對象,這段代碼給global定義了一些屬性,比如BDD模式的例子里的describe 和 it,傳入的參數都是是title + fn。所以bdd.js這個文件的作用就是給全局對象注冊bdd需要的一些接口。

          4. Reporter 報告格式

          Mocha.run 最后執行的是runner.run(), 那我們繼續分析Runner

          // lib/runner.js
          Runner.prototype.run = function (fn) {
            ...
            function start () {
              self.started = true;
              self.emit('start');
              self.runSuite(rootSuite, function () {
                debug('finished running');
                self.emit('end');
              });
            }
          
            debug('start');
            ...
            return this;
          };
          

          這里,emit 觸發 start事件,這個具體的處理在哪呢,我們全局搜索一下,發現在lib/reporters/下的每個文件基本都有on start事件,到底實際是觸發的哪一個呢?我們繼續分析一下,在Mocha目錄下發現這么一段:

          // lib/mocha.js
          /**
           * Set reporter to `reporter`, defaults to "spec".
           * ...
           */
          Mocha.prototype.reporter = function (reporter, reporterOptions) {
              ...
              reporter = reporter || 'spec';
              ...
          };
          

          所以默認的報告格式是spec,我們去 lib/reporters/spec.js 看一看:

          // lib/reporters/spec.js
          function Spec (runner) {
            ...
            runner.on('start', function () {
              console.log();
            });
            runner.on('suite', function (suite) {
              ++indents;
              console.log(color('suite', '%s%s'), indent(), suite.title);
            });
            ...
            runner.on('pass', function (test) {
              var fmt;
              if (test.speed === 'fast') {
                fmt = indent() +
                  color('checkmark', '  ' + Base.symbols.ok) +
                  color('pass', ' %s');
                console.log(fmt, test.title);
              } else {
                fmt = indent() +
                  color('checkmark', '  ' + Base.symbols.ok) +
                  color('pass', ' %s') +
                  color(test.speed, ' (%dms)');
                console.log(fmt, test.title, test.duration);
              }
            });
          
            runner.on('fail', function (test) {
              console.log(indent() + color('fail', '  %d) %s'), ++n, test.title);
            });
          
            runner.on('end', self.epilogue.bind(self));
          }
          

          可以看到這里是響應一些事件的處理,回到Runner

          // lib/runner.js
          Runner.prototype.run = function (fn) {
            ...
            function start () {
              self.started = true;
              self.emit('start');
              self.runSuite(rootSuite, function () {
                debug('finished running');
                self.emit('end');
              });
            }
          
            debug('start');
            ...
            return this;
          };
          

          這樣start對應的就是lib/reporters/spec.js里的 console.log(); 輸出空行。我們繼續往下走runSuite。

          5. RunSuite and RunTest

          // lib/runner.js
          Runner.prototype.runSuite = function (suite, fn) {
            ...
            function next (errSuite) {
            ...
            if (self._grep !== self._defaultGrep) {
              Runner.immediately(function () {
                self.runSuite(curr, next);
              });
            } else {
              self.runSuite(curr, next);
            }
          }
          
            function done (errSuite) {
                ...
                self.hook('afterAll', function () {
                  self.emit('suite end', suite);
                  fn(errSuite);
                });
              }
            }
            ...
            this.hook('beforeAll', function (err) {
              if (err) {
                return done();
              }
              self.runTests(suite, next);
            });
          };
          

          基本上是開始測試之前的準備,next作為回調函數不斷執行下一個suite,我們再繼續看看runTests:

          // lib/runner.js
          Runner.prototype.runTests = function (suite, fn) {
            ...
            function next (err, errSuite) {
          ...
              // next test
              test = tests.shift();
          ...
          
              self.hookDown('beforeEach', function (err, errSuite) {
                self.currentRunnable = self.test;
                self.runTest(function (err) {
                  ... 
                  self.emit('pass', test);
                  self.emit('test end', test);
                  self.hookUp('afterEach', next);
                });
              });
            }
            ...
            next();
          };
          

          這里是不斷調用next,執行runTest,回調函數里觸發了pass和test end事件,我們繼續看runTest:

          // lib/runner.js
          Runner.prototype.runTest = function (fn) {
            ...
            try {
              test.on('error', function (err) {
                self.fail(test, err);
              });
              test.run(fn);
            } catch (err) {
              fn(err);
            }
          };
          

          終于到了runTest了,可以看到test運行在try catch中,如果拋出錯誤,捕獲錯誤并傳入回調函數。如果成功呢?我們需要知道test.run是個啥東西。

          6. Suite and Test

          上面的過程中,不斷提到Suite和Test,這兩個東西到底是指啥呢?找源碼太麻煩了,我們直接命令行輸出一下看看,首先是suite:我在runSuite函數下console.log輸出了一下Suite,結果是:

          Suite {
              title: '',
                  ctx: {},
              suites:
                  [ Suite {
                      title: 'Test Start',
                      ctx: {},
                      suites: [Object],
                      tests: [],
                      ...
                      file: '/Users/zhangruiwu/Desktop/demo-learn/newBlog/server/test-example/index.js' } ],
                      tests: [],
                  ...
          
          Suite {
              title: 'Test Start',
                  ctx: {},
              suites:
                  [ Suite {
                      title: '加法函數測試',
                      ctx: {},
                      suites: [],
                      tests: [Object],
                      ...
          Test Start
          

          可以發現suite就是describe 生成的對象,我們測試文件中一共兩個describe,Test Start和加法函數測試,兩者是嵌套關系,在這里也體現出來了。我們繼續分析Test: 把runSuite 加的console.log(suite) 改成console.log(suite.suites[0]),結果是:

          Suite {
              title: '加法函數測試',
                  ctx: {},
              suites: [],
                  tests:
              [ {
                  "title": "1 + 1 等于 2",
                  "body": "function () {\n            return (0, _chai.expect)((0, _add2.default)(1, 1)).to.be.equal(2);\n        }",
                  ...
              },
                {
                  "title": "返回值是Number",
                  "body": "function () {\n            return (0, _chai.expect)((0, _add2.default)(1, 1)).to.be.a('number');\n        }",
                  ...
                } ],
            ...
          

          可以發現Test就是it生成的對象。Suite 和 Test對應的文件是 lib/suite.js和 lib/test.js。我們找一下test.run ,test.js 里有這么一行

          // lib/test.js
          /**
           * Inherit from `Runnable.prototype`.
           */
          Test.prototype = create(Runnable.prototype, {
            constructor: Test
          });
          

          我們再去Runnable看看:

          // lib/runnable.js
          Runnable.prototype.run = function (fn) {
            ...
            // finished
            function done (err) {
              ...
          fn(err);
            }
            ...
            done();
            ...
            };
          

          很長我就直接貼最后的代碼了。可以看到最后執行回調, 回調的內容在runTests里

          ####7. End

          // lib/runner.js
          self.runTest(function (err) {
            ...
            self.emit('pass', test);
            self.emit('test end', test);
            self.hookUp('afterEach', next);
          });
          

          pass事件在lib/reporters/spec.js中:

          // lib/reporters/spec.js
          runner.on('pass', function (test) {
            ...
              fmt = indent() +
                color('checkmark', '  ' + Base.symbols.ok) +
                color('pass', ' %s');
              console.log(fmt, test.title);
            ...
          });
          

          其中Base.symbols.ok 代表通過時候顯示的對號

          // lib/reporters/base.js
          exports.symbols = {
            ok: '?',
            ...
          };
          

          test end在lib/reporters/base.js中:

          // lib/reporters/base.js
          runner.on('test end', function () {
            stats.tests = stats.tests || 0;
            stats.tests++;
          });
          

          是用來統計test個數的。回到Runner

          // lib/runner.js
          Runner.prototype.run = function (fn) {
            ...
            function start () {
              self.started = true;
              self.emit('start');
              self.runSuite(rootSuite, function () {
                debug('finished running');
                self.emit('end');
              });
            }
          
            debug('start');
            ...
            return this;
          };
          

          發現runSuite的回調最終觸發了end事件,在lib/reporters/spec.js中:

          // lib/reporters/spec.js
          runner.on('end', self.epilogue.bind(self));
          self.epiloque在lib/reporters/base.js 中:
          // lib/reporters/base.js
          Base.prototype.epilogue = function () {
            ...
            // passes
            fmt = color('bright pass', ' ') +
              color('green', ' %d passing') +
              color('light', ' (%s)');
          
            console.log(fmt,
              stats.passes || 0,
              ms(stats.duration));
            ...
            console.log();
          };
          

          這就是最終輸出的 2 passing (71ms) 了。 到這里我們的以例子做的分析結束,但是Mocha遠遠不止這些功能,篇幅所限更多的我們就不分析了。整體上看很復雜的面向對象編程,但是結構很清晰,功能劃分明確,值得我們學習

          站長推薦

          1.阿里云: 本站目前使用的是阿里云主機,安全/可靠/穩定。點擊領取2000元代金券、了解最新阿里云產品的各種優惠活動點擊進入

          2.騰訊云: 提供云服務器、云數據庫、云存儲、視頻與CDN、域名等服務。騰訊云各類產品的最新活動,優惠券領取點擊進入

          3.廣告聯盟: 整理了目前主流的廣告聯盟平臺,如果你有流量,可以作為參考選擇適合你的平臺點擊進入

          鏈接: http://www.modern-decoration.com.cn/article/detial/141

          前端學nodejs有什么用處?

          Node.js是一個基于 Chrome V8 引擎的 JavaScript 運行環境,一個基于Chrome JavaScript運行時建立的平臺, 用于方便地搭建響應速度快、易于擴展的網絡應用。

          怎么卸載nodejs?

          Node.js是一個Javascript運行環境,可以使Javascript這類腳本語言編寫出來的代碼運行速度獲得極大提升,那么安裝后該如何卸載呢?下面本篇文章就來給大家介紹一下Windows平臺下卸載node.js的方法,希望對大家有所幫助。

          使用nodejs的好處

          Node.js是一個Javascript運行環境。Node.js 使用事件驅動, 非阻塞I/O 模型而得以輕量和高效,非常適合在分布式設備上運行數據密集型的實時應用。Node.js是單進程、單線程運行機制,通過事件輪詢

          怎么徹底刪除nodejs?

          Node.js 是一個基于Chrome JavaScript 運行時建立的一個平臺。Node.js是一個事件驅動I/O服務端JavaScript環境,基于Google的V8引擎,V8引擎執行Javascript的速度非常快,性能非常好。

          nodejs適合做什么?

          面對一個新技術,多問幾個為什么總是好的。既然 PHP、Python、Java 都可以用來進行后端開發,那么為什么還要去學習 Node.js?Node.js適合做什么?Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運行環境,一個讓 JavaScript 運行在服務端的開發平臺。

          理解 nodeJS 中的 buffer,stream

          在Node.js開發中,當遇到 buffer,stream,和二進制數據處理時,你是否像我一樣,總是感到困惑?這種感覺是否會讓你認為不了解它們,以為它們不適合你,認為而這些是Node.js作者們的事情?

          什么是node repl?

          Node REPL(Read Eval Print Loop)是Node自帶的交互式解釋器(又名Node shell),表示一個電腦的虛擬環境,類似 Window 系統的終端或 Unix/Linux shell,我們可以在終端中輸入命令,并接收系統的響應。

          node怎么更新升級?

          如果你的node安裝的比較早,現在最新的版本比自己安裝的高,則可以通過升級的方式更新到指定的版本和最新的版本。下面本篇文章就來給大家介紹windows下和linux更新升級node版本的方法。

          node modules是什么?

          在node.js中modules(模塊)與文件是一一對應的,也就是說一個node.js文件就是一個模塊,文件內容可能是我們封裝好的一些JavaScript方法、JSON數據、編譯過的C/C++拓展等,在關于node.js的誤會提到過node.js的架構

          node如何更新?

          node的更新方法:先使用npm的命令npm install -g n安裝n模塊,然后使用n stable可以將node更新到最新版本,使用n+node版本號可以將node更新到指定版本。

          內容以共享、參考、研究為目的,不存在任何商業目的。其版權屬原作者所有,如有侵權或違規,請與小編聯系!情況屬實本人將予以刪除!

          文章投稿關于web前端網站點搜索站長推薦網站地圖站長QQ:522607023

          小程序專欄: 土味情話心理測試腦筋急轉彎幽默笑話段子句子語錄成語大全運營推廣

          国产精品高清视频免费 - 视频 - 在线观看 - 影视资讯 - 唯爱网