遗落之城——Unit Test

Q:什么是Unit Test(单元测试)?

A:指对程序中最小的可测单元进行检查和验证。说白了就是测试最小可测单元,并查看测试结果是否符合自己设置的预期结果。

Q:最小可测单元又是什么?

A:一个文件、一个类、一个函数等,都可以称为最小可测单元。具体需要根据实际情况判定,一般以函数做为最小可测单元。

Q:有嘛用!?

A:保证代码的正确性、保证程序的稳定性、团队开发的硬性要求、提高自身自信心

Q:怎么做单元测试呢?

A:其实很简单,比如测试函数add = (a, b) => a + b。输入1 + 2则期待返回结果为3;输入非number类型则期待抛出异常或返回NaN。将上述测试方案放入测试模块中,就是一个完整的单元测试了。

在团队开发,尤其是大项目的开发,在项目开发完成前肯定无法对项目整体进行测试,而等到项目完成后才开始测试也是不现实的,所以单元测试就可以很好地保证项目的每一小块功能都能正常运行,这样组成的完整项目通常也就不会出大问题(而开发完成后就可以甩手给测试人员了)

不仅如此,团队合作时,团队成员尤其是leader不可能一行行读他人写的代码,运行对方写的单元测试,就可以快捷方便地知道他实现了哪些功能、这些功能是否正常运行、单元测试有没有对他写的模块覆盖完整;同时,自己写单元测试时保证了自己所写部分的正常运行,不会破坏到整体项目。

此外在上线开源项目时,单元测试覆盖率要求必须在95%以上,毕竟覆盖率太低也没人会放心用。

单元测试如此重要,可在个人开发时,我们通常只专注于功能的开发,而忽略了对功能的测试(主要还是懒得写,甚至连注释都懒得写),这种情况在公司的团队开发中也时有发生,当然也可能是团队人数有限没有更多精力,或者项目功能简单单元测试需求不高。

但考虑到如果用到单元测试,不会被一问三不知,本篇就从零开始手把手教导,做前后端的单元测试。


Node篇

后端语言我只对Node有深入学习,打算用egg框架为例,毕竟egg是企业级框架,约束规范严谨且自带单元测试功能。既然自带,那么在egg中进行单元测试就会变得很方便,省去了很多手动安装、引入、配置的繁琐过程。

egg使用Mocha作为测试框架,使用power-assert作为断言库。简单介绍下,Mocha提供了编写测试代码的方法,power-assert用来判断源代码测试结果是否符合预期;但在此之上,egg封装了egg-mock的辅助模块,配合Mocha和power-assert进行测试,具体作用就是在单元测试中帮助我们模拟app、context、cookie、session、网络请求等;而且egg提供的egg-bin工具库中也有提供进行单元测试的方法,运行egg-bin test即可。

那么首先,依据官方文档用npm init egg来初始化一个项目吧。初始化时会要求你选择一种模板类型,这里选择用simple方便演示,之后需要输入的项目相关内容中,cookie security keys代表egg必须的安全钥,egg内置了很多安全验证机制,输入一串心仪的字符串就好。

image

创建完项目安装好依赖后,你的项目会多了很多奇怪的文件,但无需关注,我们先展示下自己项目的目录结构,以及Egg的完整目录结构(初始化完成的test内部目录结构比官方图片的目录结构多了一个app层级,但并不影响)。

既然会阅读本篇,想必你在使用egg时,早就注意到Egg的test的文件夹了,只是并不知该如何使用。test文件夹就是Egg方便程序猿做单元测试而建立的文件夹,egg硬性要求test和app目录结构必须相同,说得直白点,test中的文件名必须和app文件夹中要测试的文件名相同,且必须以.test.js为后缀名,所处文件夹名也必须相同。

image
image

选择simple的好处就是egg会自带单元测试的样例模板,我们来看test/app/controller/home.test.js。

image

app:服务端项目实例对象;
assert:断言库对象;
mock:其实还可以从egg-mock/bootstrap中解构出mock对象,作用就是模拟上context、网络请求等。模板中未使用,是因为类似的方法也挂载到了app实例上,并且官方推荐使用app的对应方法;
describe:每个describe函数就是一组相关的测试,第一个参数是测试组名称,更推荐直接像模板中命名为测试文件的路径名,简单明了,但如果你的测试文件中有多组测试,可以在命名上另加区分;
it:在describe中,一个it就是一个测试用例,第一个参数也是测试名。

如何运行这个测试?见package.json文件,test-local代表运行单元测试,test代表运行单元测试并修复格式问题(也只能修复格式问题)。

image

运行测试前,有必要介绍Mocha的生命周期,Mocha生命周期的执行顺序为before => beforeEach => it => afterEach => after。顾名思义,before/after在所有it前/后执行,仅执行一次;beforeEach/afterEach在每个it前/后都要执行一次,执行次数就是it测试用例的数量。这些方法通常用作伪造数据和删除伪造数组,比如测试一些操作数据库的方法,需要一些假数据,但又不希望这些假数据保留下来,就可以在before中添加假数据,再after中删除吧假数据。

简单示范一下,终端中输入npm run test。运行完后项目中会多出run、logs文件夹,这分别包含运行配置文件和项目日志文件,egg自带记录运行日志功能,同样无需关注。

image
image

继续看代码。should assert中首先引入package.json,然后用assert断言app.config.keys必须以pkg.name属性值开头,也就是package.json中的name属性值必须和config/config.default.js中appInfo.name属性值相同,而egg会自动读取package.json的name属性,所以当断言失败时证明程序内部存在问题,比如没能正确读取。通常情况我们只要egg的约束和规范(初始化完simple项目后不要更改项目结构或文件内容骨架),就不会出现断言失败的问题。

image
image

第二个测试是模拟网络请求,向你项目中的 / 路径发送一个请求,来看app/router.js和app/controller/home.js。router将 / 路径的请求处理交给了controller文件夹的home文件的index方法,而index方法设置响应体为hi egg,这就代表请求成功了,egg会自动设置响应头,并设置成功状态码为200。那么返回的结果必然符合should get /中期待的hi egg和200,测试也会成功。可以尝试修改home.js中的响应体或home.test.js中的期待值,测试将会失败。

image
image

两种测试样例分别是同步测试和异步测试,同步和异步你肯定已经了解了,异步测试则同样包含如下三种:

1.Promise(如图中请求案例)——加return表示被测试模块可返回异步结果;
2.回调函数——接受参数done代表回调函数,expect中传入done表示被测试模块必须要有回调函数;
3.async await——表示被测试模块可等待。(使用较多)

image

额外补充一点,httpRequest是app的模拟请求的方法,app还挂在了很多方法,如mockContext(模拟ctx),可以查阅官网的测试模板理解。

测试完成且全部通过后,需要生成测试报告,在用egg脚手架创建项目时已经自动添加了该方法,运行命令也写在package.json中,只需执行npm run cov测试报告就会生成在新建的coverage/lcov-report文件夹中,本地打开index.html网页即可。

image
image

你可能感兴趣的:(遗落之城——Unit Test)