当前位置: 首页 > 后端技术 > Node.js

玩转Node.js单元测试

时间:2023-04-03 23:23:12 Node.js

在代码部署之前,进行一定的单元测试是非常有必要的,可以有效持续的保证代码质量。实践表明,高质量的单元测试还可以帮助我们改进自己的代码。本篇博客将通过一些简单的测试用例介绍几个Node.js测试模块:Mocha和Should、SuperTest。本文重点讲解原理,每个模块的详细用例后面会单独讨论。为什么需要单元测试?所谓单元测试就是验证一个功能或API的正确性。我们来看一个简单的例子add1.js:functionadd(a,b){returna+b;}是的,我写了一个加法函数。这有什么好?不妨用node来执行:>add=function(a,b){returna+b}[Function:add]>add(4)NaN当add函数只给参数4时,a为4且b未定义,两者相加为NaN。您是否考虑过只有一个参数的场景?给定参数时,NaN是您想要的结果吗?如果参数不是整数怎么办?这时候就需要进行单元测试来验证各种可能出现的场景。如果我将add函数定义为添加两个整数,而其他输入返回未定义,那么正确的代码add2.js应该是这样的:functionadd(a,b){if(typeofa==="number"&&typeofb==="数字"){返回a+b;}else{返回未定义;}}发现一个有趣的现象,我们在写代码的时候,很容易陷入思维漏洞,而在写测试的时候,往往会考虑各种情况,这就是所谓TDD(Test-Driven-Development)的神奇之处:测试驱动开发)。因此,非常有必要进行一定的单元测试:验证代码的正确性,避免修改代码时出错,避免其他团队成员修改代码时出错,方便自动化测试和部署测试框架——测试代码Mocha下面的test2.js是用来测试add2.js的。这里使用了测试框架Mocha和Node.js自带的断言库Assert。varadd=require("../add2.js");varassert=require("assert");//当两个参数都是整数时it("shouldreturn3",function(){varsum=add(1,2);assert.equal(sum,3);});//当第二个参数为String时it("shouldreturnundefined",function(){varsum=add(1,"2");assert.equal(sum,undefined);});//当只有1个参数时it("shouldreturnundefined",function(){varsum=add(1);assert.equal(sum,undefined);});测试代码使用了测试框架Mocha提供的it函数,三个it函数分别测试三种不同的用例(testcases)。it函数的第一个参数是一个字符串,用于描述测试。一般会写预期的结果,比如“shouldreturn3”;第二个参数是一个函数,用于编写测试代码。一般情况下,被调用的被测函数或API得到结果后,使用断言库判断执行结果是否正确。测试代码使用Node.js自带的断言库Assert的assert.equal函数来判断add函数返回的结果是否正确。assert.equal成功时没有任何反应,失败时抛出AssertionError。让我们用节点测试它:>assert=require("assert");>assert.equal(1,1);undefined>assert.equal(1,2);AssertionError:1==2atrepl:1:8atsigintHandlersWrap(vm.js:22:35)在sigintHandlersWrap(vm.js:96:12)在ContextifyScript.Script.runInThisContext(vm.js:21:12)在REPLServer.defaultEval(repl.js:313:29)在绑定(domain.js:280:14)atREPLServer.runBound[aseval](domain.js:293:12)atREPLServer.(repl.js:513:10)atemitOne(events.js:101:20)atREPLServer.emit(events.js:188:7)原理:我们根据Mocha的it函数一个一个的编写测试用例,然后Mocha负责执行这些用例;当assert.equal断言成功时,测试用例通过;当assert.equal断言失败时,抛出AssertionError,Mocha可以捕获这些异常,然后相应的测试用例失败。使用mocha执行test2.js:mochatest/test2.js是下面的输出,说明所有测试用例通过?应该返回3?应该返回undefined?应该返回undefined3passing而当我们使用test1.js测试add1.js,然后以下2个测试用例失败:?应返回31)应返回未定义2)应返回未定义1通过(14ms)2失败1)应返回未定义:AssertionError:'12'==undefinedatContext.(test/test1.js:18:12)2)shouldreturnundefined:AssertionError:NaN==undefinedatContext.(test/test1.js:25:12)断言库——ShouldNode提供的断言库Assert.js的功能有限。在实际工作中,Should等第三方断言库更加强大实用。我写了一个合并函数merge.js,实现了类似_.extend()和Object.assign()的功能,用于合并两个Object的属性。functionmerge(a,b){if(typeofa==="object"&&typeofb==="object"){for(varpropertyinb){a[property]=b[property];}返回一个;}else{返回未定义;}}然后我用Should写了对应的测试代码test3.js:require("should");varmerge=require("../merge.js");//当2个参数都是对象时it("应该成功”,function(){vara={name:“Fundebug”,type:“SaaS”};varb={service:“实时错误监控”,product:{frontend:“JavaScript”,backend:“Node.js",mobile:"微信小程序"}};varc=merge(a,b);c.should.have.property("name","Fundebug");c.should.have.propertyByPath("product","frontend").equal("JavaScript");});//当只有1个参数时it("shouldreturnundefined",function(){vara={name:"Fundebug",type:"SaaS"};varc=merge(a);(typeofc).should.equal("undefined");});测试代码略有点长,但是只有三个地方使用了Should:c.should.have.property("name","Fundebug");c.should.have.propertyByPath("product","frontend").equal("JavaScript");(typeofc).should.equal("undefined");可以看出Should可以:验证一个对象是否有某个属性,并验证它的值,验证一个对象是否有嵌套的属性,用链式方式验证它的那么为什么不应该不直接验证那个值c未定义?例如,这样写:c.should.equal(undefined);//这是错误的原则:should会给每个对象添加一个should属性,然后通过这个属性提供各种断言函数,我们可以通过这些函数来验证对象如果值为undefined,should不能给它添加属性,所以它失败了。通过节点验证,发现导入Should后,空对象a增加了一个should属性。>a={}>typeofa.should'undefined'>require("should")>typeofa.should'object'测试HTTP接口-SuperTestNode.js是后端开发的语言,后端开发其实很大to某种程度上,相当于写了一个HTTP接口,为前端提供服务。那么,测试HTTP接口就少不了Node.js单元测试了。我写了一个简单的HTTP接口server.jsvarhttp=require("http");varserver=http.createServer((req,res)=>{res.writeHead(200,{"Content-Type":"text/plain"});res.end("HelloFundebug");});server.listen(8000);根据Mocha的原理,测试HTTP接口不难:访问接口;获取返回数据;验证返回结果。只需使用Node.jstest4.js:require("../server.js");varhttp=require("http");varassert=require("assert");it(“应该返回你好fundebug”,函数(完成){http.get(“http://localhost:8000”,函数(res){res.setEncoding(“utf8”);res.on(“数据”,函数(text){assert.equal(res.statusCode,200);assert.equal(text,"HelloFundebug");done();});});});值得注意的是,http.get访问HTTP接口是一个异步操作。Mocha在测试异步代码的时候需要给it函数添加一个回调函数done,在断言结束的时候调用done,这样Mocha就可以知道什么时候结束测试。既然Node.js内置的模块可以测试HTTP接口,为什么还需要SuperTest呢?先看test5.js的测试代码:varrequest=require("supertest");varserver=require("../server.js");varassert=require("assert");it(“应该返回hellofundebug”,function(done){request(server).get("/").expect(200).expect(function(res){assert.equal(res.text,"HelloFundebug");}).end(完成);});对比两个测试代码,你会发现后者要简单的多。原理SuperTest封装了发送HTTP请求的接口,并提供了一个简单的expect断言来判断接口返回的结果。对于POST接口,使用SuperTest的优势会更加明显,因为使用Node.js的http模块发送POST请求是非常麻烦的。要做多少单元测试?本文编写的单元测试用例非常简单。然而,在实际工作中,单元测试是一件令人头疼的事情。修改代码有时意味着必须修改单元测试,编写新功能或API需要编写新的单元测试。如果是真的,单元测试可以写个没完没了,但这是没有意义的。根据第28条原则,20%的测试可以解决80%的问题。剩下的20%的问题,其实我们也无能为力。也就是说,通过测试来消除所有的bug是不现实的。因此,对生产代码的实时错误监控是非常有必要的,这也是我们Fundebug正在努力做的事情:)欢迎大家加入我们的Node.js技术交流群:参考链路测试框架Mocha实例教程-阮一峰单元测试如何做详细-酷壳测试原理-王印帕累托原理版权声明:转载请注明作者Fundebug及本文地址:https://blog.fundebug.com/201...