溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Node.js中怎么實(shí)現(xiàn)單元測試

發(fā)布時間:2021-07-21 09:28:22 來源:億速云 閱讀:126 作者:Leah 欄目:編程語言

Node.js中怎么實(shí)現(xiàn)單元測試,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

為啥需要單元測試?

所謂單元測試,就是對某個函數(shù)或者API進(jìn)行正確性驗(yàn)證。來看個簡單的例子add1.js:

function add(a, b) {return a + b;
}

沒錯,我寫了一個加法函數(shù)。這有啥好測的呢?不妨用node執(zhí)行一下:

> add = function(a, b){return a + b}
[Function: add] > add(4) NaN

當(dāng)add函數(shù)僅給定一個參數(shù)4的時候,a為4,b為undefined,兩者相加為NaN。

  • 你考慮過只有一個參數(shù)的場景嗎?

  • 給定一個參數(shù)時,NaN是你想要的結(jié)果嗎?

  • 如果參數(shù)不是整數(shù)怎么辦?

這時,就需要單元測試來驗(yàn)證各種可能的場景了。

如果我把a(bǔ)dd函數(shù)定義為兩個整數(shù)相加,而其他輸入則返回undefined,那么正確的代碼add2.js應(yīng)該是這樣的:

function add(a, b) {if (typeof a === "number" && typeof b === "number")
    {return a + b;
    }else{return undefined;
    }
}

發(fā)現(xiàn)一個有趣的現(xiàn)象,我們寫代碼的時候很容易陷入思維漏洞,而寫測試的時候往往會考慮各種情況,這就是所謂的TDD(Test-Driven-Development: 測試驅(qū)動開發(fā))的神奇之處。因此,進(jìn)行一定的單元測試是十分必要的:

  • 驗(yàn)證代碼的正確性

  • 避免修改代碼時出錯

  • 避免其他團(tuán)隊(duì)成員修改代碼時出錯

  • 便于自動化測試與部署

測試框架 - Mocha

下面的測試代碼test2.js用于測試add2.js。這里使用了測試框架Mocha以及Node.js自帶的斷言庫Assert。

var add = require("../add2.js");var assert  = require("assert");// 當(dāng)2個參數(shù)均為整數(shù)時it("should return 3", function() {var sum = add(1, 2);
    assert.equal(sum, 3);
});// 當(dāng)?shù)?個參數(shù)為String時it("should return undefined", function() {var sum = add(1, "2");
    assert.equal(sum, undefined);
});// 當(dāng)只有1個參數(shù)時it("should return undefined", function() {var sum = add(1);
    assert.equal(sum, undefined);
});

測試代碼中使用了測試框架Mocha提供的it函數(shù),3個it函數(shù)分別測試了3種不同的案例(test  case)。it函數(shù)的第1個參數(shù)為字符串,用于描述測試,一般會寫期望得到的結(jié)果,例如”should return 3”;  而第2個參數(shù)為函數(shù),用于編寫測試代碼,一般是先調(diào)用被測試的函數(shù)或者API,獲取結(jié)果之后,使用斷言庫判斷執(zhí)行結(jié)果是否正確。

測試代碼中使用了Node.js自帶的斷言庫Assert的assert.equal函數(shù),用于判定add函數(shù)返回的結(jié)果是否正確。assert.equal成功時不會發(fā)生什么,而失敗時會拋出一個AssertionError。不妨使用node測試一下:

> assert  = require("assert");
> assert.equal(1, 1);
undefined
> assert.equal(1, 2);AssertionError: 1 == 2at repl:1:8at sigintHandlersWrap (vm.js:22:35)
    at sigintHandlersWrap (vm.js:96:12)
    at ContextifyScript.Script.runInThisContext (vm.js:21:12)
    at REPLServer.defaultEval (repl.js:313:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:513:10)
    at emitOne (events.js:101:20)
    at REPLServer.emit (events.js:188:7)

原理:

我們按照Mocha的it函數(shù)編寫一個個測試案例,然后Mocha負(fù)責(zé)執(zhí)行這些案例;當(dāng)assert.equal斷言成功時,則測試案例通過;當(dāng)assert.equal斷言失敗時,拋出AssertionError,Mocha能夠捕獲到這些異常,然后對應(yīng)的測試案例失敗。

使用mocha執(zhí)行test2.js:

mocha test/test2.js

下面為輸出,表示測試案例全部通過

? should return 3? should return undefined? should return undefined3 passing

而當(dāng)我們使用test1.js測試add1.js時,則后面2個測試案例失敗:

? should return 3
  1) should return undefined
  2) should return undefined
  1 passing (14ms)  2 failing  1)  should return undefined:
     AssertionError: '12' == undefined  at Context.<anonymous> (test/test1.js:18:12)  2)  should return undefined:
     AssertionError: NaN == undefined  at Context.<anonymous> (test/test1.js:25:12)

斷言庫 - Should

Node.js自帶的斷言庫Assert提供的函數(shù)有限,在實(shí)際工作中,Should等第三方斷言庫則更加強(qiáng)大和實(shí)用。

我寫了一個merge函數(shù)merge.js,實(shí)現(xiàn)了類似于_.extend()與Object.assign()的功能,用于合并兩個Object的屬性。

function merge(a, b) {if (typeof a === "object" && typeof b === "object")
    {for (var property in b)
        {
            a[property] = b[property];
        }return a;
    }else{return undefined;
    }
}

然后我使用Should寫了對應(yīng)的測試代碼test3.js:

require("should");var merge = require("../merge.js");// 當(dāng)2個參數(shù)均為對象時it("should success", function() {var a = {
        name: "Fundebug",
        type: "SaaS"};var b = {
        service: "Real time bug monitoring",
        product:
        {
            frontend: "JavaScript",
            backend: "Node.js",
            mobile: "微信小程序"}
    };var c = merge(a, b);
    c.should.have.property("name", "Fundebug");
    c.should.have.propertyByPath("product", "frontend").equal("JavaScript");
});// 當(dāng)只有1個參數(shù)時it("should return undefined", function() {var a = {
        name: "Fundebug",
        type: "SaaS"};var c = merge(a);
    (typeof c).should.equal("undefined");
});

測試代碼稍微有點(diǎn)長,但是使用Should的只有三處:

c.should.have.property("name", "Fundebug");c.should.have.propertyByPath("product", "frontend").equal("JavaScript");
(typeof c).should.equal("undefined");

可知Should能夠:

  • 驗(yàn)證對象是否存在某屬性,并驗(yàn)證其取值

  • 驗(yàn)證對象是否存在某個嵌套屬性,并使用鏈?zhǔn)椒绞津?yàn)證其取值

那么Should為什么不能直接驗(yàn)證c的取值為undefined呢?比如這樣寫:

c.should.equal(undefined); // 這樣寫是錯誤的

原理:

Should會為每個對象添加should屬性,然后通過該屬性提供各種斷言函數(shù),我們可以使用這些函數(shù)驗(yàn)證對象的取值。對于undefined,Should無法為其添加屬性,因此失敗。

通過node驗(yàn)證發(fā)現(xiàn),導(dǎo)入Should之后,空對象a增加了一個should屬性。

> a = {}
> typeof a.should'undefined'> require("should")
> typeof a.should'object'

測試HTTP接口 - SuperTest

Node.js是用于后端開發(fā)的語言,而后端開發(fā)其實(shí)很大程度上等價于編寫HTTP接口,為前端提供服務(wù)。那么,Node.js單元測試則少不了對HTTP接口進(jìn)行測試。

我用Node.js自帶的HTTP模塊寫了一個簡單的HTTP接口server.js

var http = require("http");var server = http.createServer((req, res) =>
{
    res.writeHead(200,
    {"Content-Type": "text/plain"});
    res.end("Hello Fundebug");
});
server.listen(8000);

按照Mocha的原理,測試HTTP接口并不難: 訪問接口; 獲取返回數(shù)據(jù); 驗(yàn)證返回結(jié)果。使用Node.js原生的http與assert模塊就可以了test4.js:

require("../server.js");var http = require("http");var assert = require("assert");
it("should return hello fundebug", function(done) {
    http.get("http://localhost:8000", function(res)     {
        res.setEncoding("utf8");
        res.on("data", function(text)         {
            assert.equal(res.statusCode, 200);
            assert.equal(text, "Hello Fundebug");
            done();
        });
    });
});

值得稍微注意的一點(diǎn)是,http.get訪問HTTP接口是一個異步操作。Mocha在測試異步代碼是需要為it函數(shù)添加回調(diào)函數(shù)done,在斷言結(jié)束的地方調(diào)用done,這樣Mocha才能知道什么時候結(jié)束這個測試。

既然Node.js自帶的模塊就能夠測試HTTP接口了,為什么還需要SuperTest呢?不妨先看一下測試代碼test5.js:

var request = require("supertest");var server = require("../server.js");var assert = require("assert");
it("should return hello fundebug", function(done) {
    request(server)
        .get("/")
        .expect(200)
        .expect(function(res)         {
            assert.equal(res.text, "Hello Fundebug");
        })
        .end(done);
});

對比兩個測試代碼,會發(fā)現(xiàn)后者簡潔很多。

原理

SuperTest封裝了發(fā)送HTTP請求的接口,并且提供了簡單的expect斷言來判定接口返回結(jié)果。對于POST接口,使用SuperTest的優(yōu)勢將更加明顯,因?yàn)槭褂肗ode.js的http模塊發(fā)送POST請求是很麻煩的。

要做多少單元測試?

本文所寫的單元測試案例,都很簡單。然而,在實(shí)際工作中,單元測試是一個很頭痛的事情。修改了代碼有時意味著必須修改單元測試,寫了新的函數(shù)或者API就得寫新的單元測試。如果較真起來,單元測試可以沒完沒了地寫,但這是沒有意義的。而根據(jù)二八原理,20%的測試可以解決80%的問題。剩下的20%問題,事實(shí)上我們是力不從心的。換句話說,想通過測試消除所有BUG,是不現(xiàn)實(shí)的。

因此,對生產(chǎn)代碼進(jìn)行實(shí)時錯誤監(jiān)測是非常有必要的,這也是我們Fundebug努力在做的事情。

看完上述內(nèi)容,你們掌握Node.js中怎么實(shí)現(xiàn)單元測試的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI