在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ Promise 測(cè)試
Promise 測(cè)試
實(shí)戰(zhàn) Promise
用語(yǔ)集
前言
什么是 Promise
API Reference
Advanced

Promise 測(cè)試

這章我們學(xué)習(xí)如果編寫(xiě) Promise 的測(cè)試代碼

3.1. 基本測(cè)試

關(guān)于 ES6 Promises 的語(yǔ)法我們已經(jīng)學(xué)了一些, 我想大家應(yīng)該也能夠在實(shí)際項(xiàng)目中編寫(xiě) Promise 的 Demo 代碼了吧。

這時(shí),接下來(lái)你可能要苦惱該如何編寫(xiě) Promise 的測(cè)試代碼了。

那么讓我們先來(lái)學(xué)習(xí)下如何使用 Mocha 來(lái)對(duì) Promise 進(jìn)行基本的測(cè)試吧。

先聲明一下,這章中涉及的測(cè)試代碼都是運(yùn)行在 Node.js 環(huán)境下的。

本書(shū)中出現(xiàn)的示例代碼也都有相應(yīng)的測(cè)試代碼。 測(cè)試代碼可以參考 azu/promises-book 。

3.1.1. Mocha

Mocha 是 Node.js 下的測(cè)試框架工具,在這里,我們并不打算對(duì) Mochahttp://mochajs.org/ 本身進(jìn)行詳細(xì)講解。對(duì) Mocha 感興趣的讀者可以自行學(xué)習(xí)。

Mocha可以自由選擇 BDD、TDD、exports 中的任意風(fēng)格,測(cè)試中用到的 Assert 方法也同樣可以跟任何其他類(lèi)庫(kù)組合使用。 也就是說(shuō),Mocha 本身只提供執(zhí)行測(cè)試時(shí)的框架,而其他部分則由使用者自己選擇。

這里我們選擇使用 Mocha,主要基于下面 3 點(diǎn)理由。

  • 它是非常著名的測(cè)試框架
  • 支持基于 Node.js 和瀏覽器的測(cè)試
  • 支持"Promise測(cè)試"

最后至于為什么說(shuō) 支持"Promise測(cè)試" ,這個(gè)我們?cè)诤竺嬖僦v。

要想在本章中使用 Mocha,我們需要先通過(guò) npm 來(lái)安裝 Mocha。


   $ npm install -g mocha

另外,Assert 庫(kù)我們使用的是 Node.js 自帶的 assert 模塊,所以不需要額外安裝。

首先,讓我們?cè)囍帉?xiě)一個(gè)對(duì)傳統(tǒng)回調(diào)風(fēng)格的異步函數(shù)進(jìn)行測(cè)試的代碼。

3.1.2. 回調(diào)函數(shù)風(fēng)格的測(cè)試

如果想使用回調(diào)函數(shù)風(fēng)格來(lái)對(duì)一個(gè)異步處理進(jìn)行測(cè)試,使用 Mocha 的話代碼如下所示。


    var assert = require('power-assert');
    describe('Basic Test', function () {
    context('When Callback(high-order function)', function () {
    it('should use `done` for test', function (done) {
    setTimeout(function () {
    assert(true);
    done();
    }, 0);
    });
    });
    context('When promise object', function () {
    it('should use `done` for test?', function (done) {
    var promise = Promise.resolve(1);
    // このテストコードはある欠陥があります
    promise.then(function (value) {
    assert(value === 1);
    done();
    });
    });
    });
    });

將這段代碼保存為 basic-test.js,之后就可以使用剛才安裝的 Mocha 的命令行工具進(jìn)行測(cè)試了。


    $ mocha basic-test.js

Mocha 的 it 方法指定了 done 參數(shù),在 done() 函數(shù)被執(zhí)行之前, 該測(cè)試一直處于等待狀態(tài),這樣就可以對(duì)異步處理進(jìn)行測(cè)試。

Mocha 中的異步測(cè)試,將會(huì)按照下面的步驟執(zhí)行。


    it("should use `done` for test", function (done) {
    <1>
        setTimeout(function () {
            assert(true);
            done();<2>
        }, 0);
    });

<1> 回調(diào)式的異步處理
<2> 調(diào)用 one 后測(cè)試結(jié)束

這也是一種非常常見(jiàn)的實(shí)現(xiàn)方式。

3.1.3. 使用 done 的 Promise 測(cè)試

接下來(lái),讓我們看看如何使用 done 來(lái)進(jìn)行 Promise 測(cè)試。


    it("should use `done` for test?", function (done) {
         var promise = Promise.resolve(42);<1>
        promise.then(function (value) {
            assert(value === 42);<2>
            done();
        });
    });

<1>創(chuàng)建名為 Fulfilled 的 promise 對(duì)象
<2>調(diào)用 done 后測(cè)試結(jié)束

Promise.resolve 用來(lái)返回 promise 對(duì)象, 返回的 promise 對(duì)象狀態(tài)為 FulFilled。 最后,通過(guò) .then 設(shè)置的回調(diào)函數(shù)也會(huì)被調(diào)用。

專(zhuān)欄: Promise 只能進(jìn)行異步操作? 中已經(jīng)提到的那樣, promise 對(duì)象的調(diào)用總是異步進(jìn)行的,所以測(cè)試也同樣需要以異步調(diào)用的方式來(lái)編寫(xiě)。

但是,在前面的測(cè)試代碼中,在 assert 失敗的情況下就會(huì)出現(xiàn)問(wèn)題。

對(duì)異常promise測(cè)試


    it("should use `done` for test?", function (done) {
       var promise = Promise.resolve();
        promise.then(function (value) {
            assert(false);// => throw AssertionError
            done();
        });
    });

在此次測(cè)試中 assert 失敗了,所以你可能認(rèn)為應(yīng)該拋出“測(cè)試失敗”的錯(cuò)誤, 而實(shí)際情況卻是測(cè)試并不會(huì)結(jié)束,直到超時(shí)。

http://wiki.jikexueyuan.com/project/javascript-promise-mini-book/images/7.png" alt="picture7" />

Figure 7. 由于測(cè)試不會(huì)結(jié)束,所以直到發(fā)生超時(shí)時(shí)間未知,一直會(huì)處于掛起狀態(tài)。

通常情況下,assert 失敗的時(shí)候,會(huì) throw 一個(gè) error, 測(cè)試框架會(huì)捕獲該 error,來(lái)判斷測(cè)試失敗。

但是,Promise 的情況下 .then 綁定的函數(shù)執(zhí)行時(shí)發(fā)生的 error 會(huì)被 Promise 捕獲,而測(cè)試框架則對(duì)此 error 將會(huì)一無(wú)所知。

我們來(lái)改善一下 assert 失敗的 promise 測(cè)試, 讓它能正確處理 assert 失敗時(shí)的測(cè)試結(jié)果。

測(cè)試正常失敗的示例


    it("should use `done` for test?", function (done) {
        var promise = Promise.resolve();
        promise.then(function (value) {
            assert(false);
        }).then(done, done);
    });

在上面測(cè)試正常失敗的示例中,為了確保 done 一定會(huì)被調(diào)用, 我們?cè)谧詈筇砑恿?.then(done, done); 語(yǔ)句。

assert 測(cè)試通過(guò)(成功)時(shí)會(huì)調(diào)用 done() ,而 assert 失敗時(shí)則調(diào)用 done(error) 。

這樣,我們就編寫(xiě)出了和 回調(diào)函數(shù)風(fēng)格的測(cè)試 相同的 Promise 測(cè)試。

但是,為了處理 assert 失敗的情況,我們需要額外添加 .then(done, done); 的代碼。 這就要求我們?cè)诰帉?xiě) Promise 測(cè)試時(shí)要格外小心,忘了加上上面語(yǔ)句的話,很可能就會(huì)寫(xiě)出一個(gè)永遠(yuǎn)不會(huì)返回直到超時(shí)的測(cè)試代碼。

在下一節(jié),讓我們接著學(xué)習(xí)一下最初提到的使用 Mocha 理由中的支持" Promises 測(cè)試"究竟是一種什么機(jī)制。

3.2. Mocha 對(duì) Promise 的支持

在這里,我們將會(huì)學(xué)習(xí)什么是 Mocha 支持的“對(duì) Promise 測(cè)試”。

官方網(wǎng)站 Asynchronous code 也記載了關(guān)于 Promise 測(cè)試的概要。

Alternately, instead of using the done() callback, you can return a promise. This is useful if the APIs you are testing return promises instead of taking callbacks:

這段話的意思是,在對(duì) Promise 進(jìn)行測(cè)試的時(shí)候,不使用 done() 這樣的回調(diào)風(fēng)格的代碼編寫(xiě)方式,而是返回一個(gè) promise 對(duì)象。

那么實(shí)際上代碼將會(huì)是什么樣的呢?這里我們來(lái)看個(gè)具體的例子應(yīng)該容易理解了。


    var assert = require('power-assert');
    describe('Promise Test', function () {
        it('should return a promise object', function () {
            var promise = Promise.resolve(1);
            return promise.then(function (value) {
                assert(value === 1);
            });
        });
    });

這段代碼將前面 前面使用 done 的例子 按照 Mocha 的 Promise 測(cè)試方式進(jìn)行了重寫(xiě)。

修改的地方主要在以下兩點(diǎn):

  • 刪除了 done
  • 返回結(jié)果為 promise 對(duì)象

采用這種寫(xiě)法的話,當(dāng) assert 失敗的時(shí)候,測(cè)試本身自然也會(huì)失敗。


    t("should be fail", function () {
        return Promise.resolve().then(function () {
            assert(false);// => 測(cè)試失敗
        });
    });

采用這種方法,就能從根本上省略諸如 .then(done, done); 這樣本質(zhì)上跟測(cè)試邏輯并無(wú)直接關(guān)系的代碼。

Mocha 已經(jīng)支持對(duì) Promises 的測(cè)試 | Web scratch 這篇(日語(yǔ))文章里也提到了關(guān)于 Mocha 對(duì) Promise 測(cè)試的支持。

3.2.1. 意料之外(失敗的)的測(cè)試結(jié)果

因?yàn)?Mocha 提供了對(duì) Promise 的測(cè)試,所以我們會(huì)認(rèn)為按照 Mocha 的規(guī)則來(lái)寫(xiě)會(huì)比較好。 但是這種代碼可能會(huì)帶來(lái)意想不到的異常情況的發(fā)生。

比如對(duì)下面的 mayBeRejected() 函數(shù)的測(cè)試代碼,該函數(shù)返回一個(gè)當(dāng)滿足某一條件就變?yōu)?Rejected 的 promise 對(duì)象。


    function mayBeRejected(){ <1>
        return Promise.reject(new Error("woo"));
    }
    it("is bad pattern", function () {
        return mayBeRejected().catch(function (error) {
            assert(error.message === "woo");
        });
    });

<1>這個(gè)函數(shù)用來(lái)對(duì)返回的 promise 對(duì)象進(jìn)行測(cè)試

這個(gè)測(cè)試的目的包括以下兩點(diǎn):

mayBeRejected() 返回的 promise 對(duì)象如果變?yōu)?FulFilled 狀態(tài)的話 測(cè)試將會(huì)失敗

mayBeRejected() 返回的promise 對(duì)象如果變?yōu)?Rejected 狀態(tài)的話assert 中對(duì) Error 對(duì)象進(jìn)行檢查

上面的測(cè)試代碼,當(dāng) promise 對(duì)象變?yōu)?Rejected 的時(shí)候,會(huì)調(diào)用在 onRejected 中注冊(cè)的函數(shù),從而沒(méi)有走正 promise 的處理常流程,測(cè)試會(huì)成功。

這段測(cè)試代碼的問(wèn)題在于當(dāng) mayBeRejected() 返回的是一個(gè) 為 FulFilled 狀態(tài)的 promise 對(duì)象時(shí),測(cè)試會(huì)一直成功。


    function mayBeRejected(){ <1>
        return Promise.resolve();
    }
    it("is bad pattern", function () {
        return mayBeRejected().catch(function (error) {
            assert(error.message === "woo");
        });
    });

<1> 返回的 promise 對(duì)象會(huì)變?yōu)?FulFilled

在這種情況下,由于在 catch 中注冊(cè)的 onRejected 函數(shù)并不會(huì)被調(diào)用,因此 assert 也不會(huì)被執(zhí)行,測(cè)試會(huì)一直通過(guò)(passed,成功)。

為了解決這個(gè)問(wèn)題,我們可以在 .catch 的前面加入一個(gè) .then 調(diào)用,可以理解為如果調(diào)用了 .then 的話,那么測(cè)試就需要失敗


    function failTest() { <1>
        throw new Error("Expected promise to be rejected but it was fulfilled");
    }
    function mayBeRejected(){
        return Promise.resolve();
    }
    it("should bad pattern", function () {
        return mayBeRejected().then(failTest).catch(function (error) {
            assert.deepEqual(error.message === "woo");
        });
    });

<1>通過(guò) throw 來(lái)使測(cè)試失敗

但是,這種寫(xiě)法會(huì)像在前面 then or catch? 中已經(jīng)介紹的一樣, failTest 拋出的異常會(huì)被 catch 捕獲。

http://wiki.jikexueyuan.com/project/javascript-promise-mini-book/images/8.png" alt="picture8" />

Figure 8. Then Catch flow

程序的執(zhí)行流程為 thencatch,傳遞給 catch 的 Error 對(duì)象為 AssertionError 類(lèi)型 , 這并不是我們想要的東西。

也就是說(shuō),我們希望測(cè)試只能通過(guò)狀態(tài)會(huì)變?yōu)?onRejected 的 promise 對(duì)象, 如果 promise 對(duì)象狀態(tài)為 onFulfilled 狀態(tài)的話,那么該測(cè)試就會(huì)一直通過(guò)。

3.2.2. 明確兩種狀態(tài),改善測(cè)試中的意外(異常)狀況

在編寫(xiě) 上面對(duì) Error 對(duì)象進(jìn)行測(cè)試的例子 時(shí), 怎么才能剔除那些會(huì)意外通過(guò)測(cè)試的情況呢?

最簡(jiǎn)單的方式就是像下面這樣,在測(cè)試代碼中判斷在各種 promise 對(duì)象的狀態(tài)下,應(yīng)進(jìn)行如何的操作。

變?yōu)?FulFilled 狀態(tài)的時(shí)候 測(cè)試會(huì)預(yù)期失敗

變?yōu)?Rejected 狀態(tài)的時(shí)候 使用 assert 進(jìn)行測(cè)試

也就是說(shuō),我們需要在測(cè)試代碼中明確指定在Fulfilled和Rejected這兩種狀態(tài)下,都需進(jìn)行什么樣的處理。


    function mayBeRejected() {
        return Promise.resolve();
    }
    it("catch -> then", function () {
        // 變?yōu)镕ulFilled的時(shí)候測(cè)試失敗
        return mayBeRejected().then(failTest, function (error) {
            assert(error.message === "woo");
        });
    });

像這樣的話,就能在 promise 變?yōu)?FulFilled 的時(shí)候編寫(xiě)出失敗用的測(cè)試代碼了。

http://wiki.jikexueyuan.com/project/javascript-promise-mini-book/images/9.png" alt="picture9" />

Figure 9. Promise onRejected test

then or catch? 中我們已經(jīng)講過(guò),為了避免遺漏對(duì)錯(cuò)誤的處理, 與使用 .then(onFulfilled, onRejected) 這樣帶有二個(gè)參數(shù)的調(diào)用形式相比, 我們更推薦使用 thencatch 這樣的處理方式。

但是在編寫(xiě)測(cè)試代碼的時(shí)候,Promise 強(qiáng)大的錯(cuò)誤處理機(jī)制反而成了限制我們的障礙。 因此我們不得已采取了 .then(failTest, onRejected) 這種寫(xiě)法,明確指定 promise 在各種狀態(tài)下進(jìn)行何種的處理。

3.2.3. 總結(jié)

在本小節(jié)中我們對(duì)在使用 Mocha 進(jìn)行 Promise 測(cè)試時(shí)可能出現(xiàn)的一些意外情況進(jìn)行了介紹。

  • 普通的代碼采用 thencatch 的流程的話比較容易理解

    • 這是為了錯(cuò)誤處理的方便。請(qǐng)參考 then or catch?
  • 將測(cè)試代碼集中到 then 中處理
    • 為了能將 AssertionError 對(duì)象傳遞到測(cè)試框架中。

通過(guò)使用 .then(onFulfilled, onRejected) 這種形式的寫(xiě)法, 我們可以明確指定 promise 對(duì)象在變?yōu)?Fulfilled 或 Rejected 時(shí)如何進(jìn)行處理。

但是,由于需要顯示的指定 Rejected 時(shí)的測(cè)試處理, 像下面這樣的代碼看起來(lái)總是有一些讓人感到不太直觀的感覺(jué)。


    promise.then(failTest, function(error){
    // 使用assert對(duì)error進(jìn)行測(cè)試
    });

在下一小節(jié),我們會(huì)介紹如何編寫(xiě) helper 函數(shù)以方便編寫(xiě) Promise 的測(cè)試代碼, 以及怎樣去編寫(xiě)更容易理解的測(cè)試代碼。

3.3. 編寫(xiě)可控測(cè)試(controllable tests)

在繼續(xù)進(jìn)行說(shuō)明之前,我們先來(lái)定義一下什么是可控測(cè)試。在這里我們對(duì)可控測(cè)試的定義如下。

待測(cè)試的 promise 對(duì)象

  • 如果編寫(xiě)預(yù)期為 Fulfilled 狀態(tài)的測(cè)試的話

    • Rejected 的時(shí)候要 Fail
    • assertion 的結(jié)果不一致的時(shí)候要 Fail
  • 如果預(yù)期為 Rejected 狀態(tài)的話
    • 結(jié)果為 Fulfilled 測(cè)試為 Fail
    • assertion 的結(jié)果不一致的時(shí)候要 Fail

如果一個(gè)測(cè)試能網(wǎng)羅上面的用例(Fail)項(xiàng),那么我們就稱(chēng)其為可控測(cè)試。

也就是說(shuō),一個(gè)測(cè)試用例應(yīng)該包括下面的測(cè)試內(nèi)容。

  • 結(jié)果滿足 Fulfilled or Rejected 之一
  • 對(duì)傳遞給 assertion 的值進(jìn)行檢查

在前面使用了 .then 的代碼就是一個(gè)期望結(jié)果為 Rejected 的測(cè)試。


    promise.then(failTest, function(error){
        // 通過(guò)assert驗(yàn)證error對(duì)象
        assert(error instanceof Error);
    });

3.3.1. 必須明確指定轉(zhuǎn)換后的狀態(tài)

為了編寫(xiě)有效的測(cè)試代碼, 我們需要明確指定 promise 的狀態(tài) 為 Fulfilled or Rejected 的兩者之一。

但是由于 .then 的話在調(diào)用的時(shí)候可以省略參數(shù),有時(shí)候可能會(huì)忘記加入使測(cè)試失敗的條件。

因此,我們可以定義一個(gè) helper 函數(shù),用來(lái)明確定義 promise 期望的狀態(tài)。

筆者(原著者)創(chuàng)建了一個(gè)類(lèi)庫(kù) azu/promise-test-helper 以方便對(duì) Promise 進(jìn)行測(cè)試,本文中使用的是這個(gè)類(lèi)庫(kù)的簡(jiǎn)略版

首先我們創(chuàng)建一個(gè)名為 shouldRejected 的 helper 函數(shù),用來(lái)在剛才的 .then 的例子中,期待測(cè)試返回狀態(tài)為 onRejected 的結(jié)果的例子。


    var assert = require('power-assert');
    function shouldRejected(promise) {
    return {
    'catch': function (fn) {
    return promise.then(function () {
    throw new Error('Expected promise to be rejected but it was fulfilled');
    }, function (reason) {
    fn.call(promise, reason);
    });
    }
    };
    }
    it('should be rejected', function () {
    var promise = Promise.reject(new Error('human error'));
    return shouldRejected(promise).catch(function (error) {
    assert(error.message === 'human error');
    });
    });

shouldRejected 函數(shù)接收一個(gè) promise 對(duì)象作為參數(shù),并且返回一個(gè)帶有 catch 方法的對(duì)象。

在這個(gè) catch 中可以使用和 onRejected 里一樣的代碼,因此我們可以在 catch 使用基于 assertion 方法的測(cè)試代碼。

shouldRejected 外部,都是類(lèi)似如下、和普通的 promise 處理大同小異的代碼。

  1. 將需要測(cè)試的 promise 對(duì)象傳遞給 shouldRejected 方法
  2. 在返回的對(duì)象的 catch 方法中編寫(xiě)進(jìn)行 onRejected 處理的代碼
  3. 在 onRejected 里使用 assertion 進(jìn)行判斷

在使用 shouldRejected 函數(shù)的時(shí)候,如果是 Fulfilled 被調(diào)用了的話,則會(huì) throw 一個(gè)異常,測(cè)試也會(huì)失敗。


    promise.then(failTest, function(error){
        assert(error.message === 'human error');
    });
        // == 幾乎這兩段代碼是同樣的意思
    shouldRejected(promise).catch(function (error) {
        assert(error.message === 'human error');
    });

使用 shouldRejected 這樣的 helper 函數(shù),測(cè)試代碼也會(huì)變得很直觀。

http://wiki.jikexueyuan.com/project/javascript-promise-mini-book/images/10.png" alt="picture10" />

Figure 10. Promise onRejected test

像上面一樣,我們也可以編寫(xiě)一個(gè)測(cè)試 promise 對(duì)象期待結(jié)果為 Fulfilled 的 shouldFulfilled helper 函數(shù)。


    var assert = require('power-assert');
    function shouldFulfilled(promise) {
    return {
    'then': function (fn) {
    return promise.then(function (value) {
    fn.call(promise, value);
    }, function (reason) {
    throw reason;
    });
    }
    };
    }
    it('should be fulfilled', function () {
    var promise = Promise.resolve('value');
    return shouldFulfilled(promise).then(function (value) {
    assert(value === 'value');
    });
    });

這和上面的 shouldRejected-test.js 結(jié)構(gòu)基本相同,只不過(guò)返回對(duì)象的 catch 方法變?yōu)榱?then ,promise.then 的兩個(gè)參數(shù)也調(diào)換了。

3.3.2. 小結(jié)

在本小節(jié)我們學(xué)習(xí)了如何編寫(xiě)針對(duì) Promise 特定狀態(tài)的測(cè)試代碼,以及如何使用便于測(cè)試的 helper 函數(shù)。

這里我們使用到的 shouldFulfilledshouldRejected 也可以在下面的類(lèi)庫(kù)中找到。 azu/promise-test-helper。

此外,本小節(jié)中的 helper 方法都是以 Mocha 對(duì) Promise 的支持 為前提的, 在 基于 done 的測(cè)試 中使用的話可能會(huì)比較麻煩。

是使用基于測(cè)試框架對(duì) Promis 的支持,還是使用基于類(lèi)似 done 這樣回調(diào)風(fēng)格的測(cè)試方式,每個(gè)人都可以自由的選擇,只是風(fēng)格問(wèn)題,我覺(jué)得倒沒(méi)必要去爭(zhēng)一個(gè)孰優(yōu)孰劣。

比如在 CoffeeScript 下進(jìn)行測(cè)試的話,由于 CoffeeScript 會(huì)隱式的使用 return 返回,所以使用 done 的話可能更容易理解一些。

對(duì) Promise 進(jìn)行測(cè)試比對(duì)通常的異步函數(shù)進(jìn)行測(cè)試坑更多,雖說(shuō)采取什么樣的測(cè)試方法是個(gè)人的自由,但是在同一項(xiàng)目中采取前后風(fēng)格一致的測(cè)試則是非常重要。

上一篇:什么是 Promise下一篇:Advanced