素材牛VIP会员
最佳实践:使用NodeJS进行web开发,如何优雅地进行数据层的单元测试?
 陈***8  分类:Node.js  人气:851  回帖:3  发布于6年前 收藏

仔细想了下,觉得我问的这个问题维度可大可小,我就列一下我的一些困扰吧,求给建议或者分享经验。

如何确定测试的维度?

以一个常见的web应用为例,我们一般会有数据层(完全对数据库的操作封装),面向客户端的业务接口,可能还会有面向第三方应用的API。那么如此一般你们会对那些维度进行测试呢?比如:

  • 数据层做单元测试?
  • 针对API做http接口测试?

纠结主要在于虽然是不同层的测试,但是其实有非常多得荣誉在里面。比如数据层的一个createUser 方法的测试,对于API的HTTP层,无非可能直接就是把用户输入传到这个方法里面,然后返回这个方法的结果给客户端。

测试的具体编写你们一般怎么搞?

目前来说这些测试我都是手写,不知道大家是否有更加自动化的方式做这些测试。手写的话,我个人感觉问题有:

  • 机械重复劳动:比如你的业务中有userappproject这些事例,那么这三者的所谓"增删改查"的用例基本上千篇一律,可能你写完user,后面的appproject基本上就是复制过来然后改改名字了。
  • 模拟数据构造的繁琐
  • 缺少最佳实践的指导,比如下面这样的情况:
jsdescribe( 'common', function(){
    it( '添加用户', function( done ){
        done();
    });

    it( '获取用户列表', function( done ){
        done();
    });

    it( '获取某个用户', function( done ){
        done();
    });

    it( '更新用户', function( done ){
        done();
    });

    it( '删除用户', function( done ){
        done();
    });
});

感觉应该是比较常见的case,那么除了第一个“添加用户”外,可以看到,其他得所有case都需要“某个用户已经被添加”为前提。那么一般大家是怎么操作呢?我想到的思路有两种:

  • 方案一:在每个case里面自己独立 添加一个新用户,结束的时候再删除掉这个用户(可以使用mocha的beforeEachafterEach
  • 方案二:创建一个全局的新用户变量,在第一个case跑完后,复制到这个变量中,后续的case就直接使用这个变量即可

欢迎发散分享经验!!!!

讨论这个帖子(3)垃圾回帖将一律封号处理……

Lv4 码徒
风***j 技术总监 6年前#1

我的理解是:啥不变测试啥
举个例子你测试api,application public interface (short for api),这个就是不变的,因为你public了,别人都在用,你如果接口改来改去,别人针对你的api调用的,能不报错吗?
单元测试其实测试也是这个[object or element or entry] interface,不会有人测试private method or protected method吧
针对业务逻辑的测试也是一样,为啥,通常情况下,product没有release的开发阶段,业务逻辑应该是稳定,否则你怎么做,东西还没有做完,需求已经变了

实践:找出业务逻辑来测试,如果你的业务逻辑写在model里面那就测试model,写在service里,你就测试service,
通常一个api应该是对应着一个业务逻辑的,你测试了业务逻辑,自然就不用测试api了比如
user->getuser() 这个method会对应着一个http://domain.com/getuser.xxxx的api
这也是RESTful的由来我的理解,ror里面的一个method 会自动映射成一个webservice or api,让人调用

最后在强调一遍:不要那么教条,所有的测试应该基于这个原则【啥不变测试啥】,如果东西一直在变,你怎么测

Lv6 码匠
co***ky Linux系统工程师 6年前#2

我是这样做的 API测试 这个没办法 必须得做
如果你的ORM活着类似的数据引擎 是自己开发的 保证这个没问题就好了
复杂的数据操作 做一些单元测试 如果只是很简单的功能 就没必要了
当然TDD的话除外 对于创业团队 测试很难特别全面的 掌握好一个度就好了~

Lv6 码匠
马***0 软件测试工程师 6年前#3

按照我的经验:

  • 测试要以「业务流程」为单位,而不是以「接口」为单位
  • 不同的测试之间不要共享数据,尽可能不要使用全局变量
  • 业务逻辑尽量往 Model 里写(或者单独抽象出一个 Service 层),不要写在 Controller
  • 如果发现重复的测试比较多的话,可以先不写测试(或只对其中一个地方写测试),等到发现某个功能有问题,再针对问题写测试

所谓「业务流程」就是指类似「一个普通用户注册帐号、登录、修改密码、发帖、回复」或者「一个管理员账户登录管理员面板、发帖、回复、删除别人的发帖」的流程。这样的好处有很多,比如前面第一个流程的每个测试之间都共享同一个「普通用户」,第二个流程都共享一个「管理员用户」。

在一个流程内部共享数据可控性比较强,因为一个流程一般不会太长,而且内部的联系是比较大的。如果针对每个接口做测试的话,要么为每个测试单独准备数据(很繁琐),要么在所有测试之间共享数据(会出现很多全局变量,会很大程度上增加复杂度,测试之间互相还可能出现干扰)。

应该保证每一个测试文件里的测试是独立的,用 before 来定义这个文件所依赖的数据(比如这个文件的所有测试都需要先有一个用户),你可以在其他文件定义一个 createTestAccount 的函数,接受一些选项,然后来生成符合要求的测试数据。这样的好处就是你可以单独运行某个测试,如果运行整个测试比较耗时的话,这样可以解决很多时间,而且你也不必担心测试之间会互相干扰。而且比如 mocha 这样的库,它是不担保不同测试文件之间的运行顺序的(虽然实际会按字母顺序运行),如果不同的测试文件之间有依赖会很麻烦。

至于究竟是测 API 接口,还是测 Model, 这个就比较见仁见智了,不过「尽量把业务逻辑写到 Model」里这点是不会变的。

如果是测 API 的话,那么测试可以直接调用 Model 里的方法来准备数据和验证测试结果(比起用 API 来准备数据和验证结果,可是要方便多了);如果是测 Model 的话,因为大部分逻辑在 Model 里,所以 Model 测完就可以基本保证没有大问题了,因为 Controller 里的逻辑不多。

最后,请一定不要用「代码生成器」来生成测试代码。能够用脚本生成,说明这些测试之间是有内在的逻辑的,你完全可以通过好的设计来避免出现重复的代码,毕竟 mocha 的测试也都是 JavaScript 代码,有什么不能实现的呢?当然,测试的抽象程度不能太高了,要稍微直白一点,否则就会在「调试测试」上花太多时间,这之间需要自行做个权衡。

关于「代码生成器」请参考 程序员修炼之道:从小工到专家 一书中的「邪恶的向导」一节,这本书中亦介绍了大量编写自动测试的技巧。

 文明上网,理性发言!   😉 阿里云幸运券,戳我领取