gulp karma webpack babel jasmine 前端单元测试工作流框架

我在写componer的最后一关,就是实现单元测试的部分,前端的单元测试其实比较难,因为前端和服务端的代码不一样,无法做到细节的监控,前端的测试大多从用户如何如何操作,应该收到什么什么效果出发去写,所以往往会出现一些意想不到的测试上的问题。

吐槽的差不多,接下来就详细的介绍一下使用gulp作为工作流控制工具,利用karma作为测试执行过程管理工具(Test Runner),webpack作为解析器打包源码和测试代码,jasmine作为测试框架的一个构建。babel作为webpack的一个加载器,解析我们源码中的ES6代码。

本文所有的讲解,都可以参考我写的example,边看代码边阅读更有助于理解。

在gulp中使用karma

假设你已经比较了解gulp了,(如果你不是很了解,可以点击我博客上方导航栏里面的“书”栏目,进入《gulp中文文档》进行学习。)gulp说到底,是一个工作流控制工具,所谓工作流,就是说你在写代码的时候,代码的创建、编译、测试等这些过程可以通过gulp来进行控制。简单的例子就是你自己编写好build的控制代码,然后执行gulp build,就可以完成所有的编译,而不需要自己手动去用命令行执行babel、webpack等命令。从我刚入门的角度来看,可以把gulp当做一个触发器,执行gulp命令,gulp又去执行其他的任务,相当于不用自己亲自执行各种复杂的任务组合。

既然理解为触发器,那么在gulp中使用karma就很好理解,相当于gulp触发karma,后面的事都是karma去做。所以我们要先编写好karma的配置,然后在gulpfile的体系中编写好触发karma的代码,这样执行gulp即可自动触发执行karma的测试动作。

下面是一个我写的例子,或者看这里

import {server as karma} from "gulp-karma-runner"

gulp.task("test", () => {
    gulp.src("test/specs/**/*.js")
        .pipe(karma({
            // config of karma
        }))
        .on("end", () => console.log("Done!"))
})

上面借助了gulp-karma-runner这个插件。karma的官方是说,在gulp中根本不需要借助插件,因为在gulp中使用karma超级简单,只需要new一个就可以,具体可以去karma的github上看他的例子。但我为了统一在gulp中使用src的方式,所以用了上面这种方法。而且gulp-karma-runner还有一个好处,是可以自己手动去配置karma的参数,而不像karma常用的方法写一个config文件。

karma中使用jasmine

karma在执行测试的时候,会自己创建一个html去跑一遍,然后监控这个html中jasmine框架给出的测试结果,得到这些结果之后就得到了测试的结果。但是在创建这个html的时候是怎样的呢?你怎么知道会不会跑一遍我要测试的代码呢?

这个你可能得去看下jasmine的文档。简单的说,就是你得在你的html中先引入jasmine框架,就像引入jquery或angular一样,然后再引入你要测试的代码,最后再引入你的测试代码,也就是按照jasmine的describe/it的语法撰写的代码。

jasmine即使不在karma中也可以自己跑,你自己去看jasmine官网的例子。现在的问题是,karma中jasmine是怎么用的?前面已经提到了,karma会自己创建一个临时的html文档,并且调用浏览器去打开这个html(如果给了启动器的话,没有给启动器的情况下,karma会给你一个地址,让你自己去打开这个地址,也可以看到这个html,通过F12调试模式也可以看到测试结果),执行完之后,把所有的测试结果收集起来。其实karma到这里就结束了自己的工作,但是我们希望得到更多东西,所以会给karma装一堆插件,这样可以让karma自动打开浏览器进行测试,收集测试以后创建报告,甚至可以通过一些插件控制测试代码,比如后面我们要讲的利用webpack来打包测试代码。

所以karma中使用jasmine其实超级简单,首先是安装一个karma-jasmine插件,然后在karma的配置中加载这个插件,并且使用jasmine作为测试框架。这样配置好之后,在karma创建的临时测试html文档中,就会自动安排好jasmine框架的引入。

说到这里,其实karma跟gulp是有一点像的,gulp是控制任意的工作流,没有任何自己的东西输出,而karma是控制测试的工作流,输出一个临时的html,当然,除了这个html,还有背后的server,因为它要收集测试结果,所以这个server就在后台接收这些结果,临时保存起来,方便插件获取这些结果。但是其实没有karma也可以完成测试,只是不够方便。

来看下karma中怎么去配置jasmine的东西,这里也有例子

{
    frameworks: ["jasmine"],
    plugins: [
       require("karma-jasmine"), 
    ],
}

在karma的配置中,就这里两个配置就可以使用jasmine了,你可以试试使用karma的debug,看看它的临时html中是不是已经把jasmine引入进来了。

karma中使用webpack

webpack又是一个重量级的家伙,它是一个打包工具,所以现在被捧的火热,已经超越了打包的范畴,也是个工作流的东西,甚至有人要用webpack代替gulp,但我觉得一个东西坚持自己的核心功能,才能走的更远,火两年就死掉的工具或框架也不计其数。

现在的问题是,为什么要在karma里面使用webpack打包?因为我们大部分情况下,都是使用模块化的方式写的源码,源码中有大量require的东西,直接在浏览器下没法用。webpack的工作,就是将这些require来require去的各个模块打包为一个文件,并且提供内置的require替代方法,这样在这个文件内部就可以实现模块化类似的require方法,但同时也可以在浏览器下运行。webpack最大的好处,就是你的源码完全按照模块化编程,require随便用,当然在ES6语法中并没有require。

webpack的特点就是输出一个js文件,把所有的资源都囊括在这个js里面,包括css甚至图片。按理来说,我们在karma的临时html里面,可以用webpack把源码打个包作为一个文件,然后把test源码打个包作为测试文件,这样可以分开,但是karma中使用webpack是以插件的形式,所以进入webpack进行打包的文件就是karma里面配置的入口文件,所以这会导致webpack会把所有karma的入口文件打包成为一个文件。这也就是为什么我的example里面,karma的入口文件只有test/specs/**/*.js,因为我只需要在这些test文件里面require源码和依赖包,所有的东西都会在这个打包出来的js文件里面。所以,这反而让事情变得简单了,不需要我们去考虑哪些文件是不是要引入之类的问题。直接在你的测试文件里面写require就可以了,看下例子吧。

karma中关于webpack的配置项多了几个,具体看这里

{
    preprocessors: {
        "test/speces/**/*.js": ["webpack"],
    },
    webpack: {
        // webpack config
    },
    plugins: [
        require("karma-webpack"),
    ],
}

preprocessors这个选项是告诉karma,在你输出临时html之前,应该进行一些什么样的处理。这里就是告诉karma,你应该事先用webpack去对"test/sepeces/**/*.js"这些文件进行打包。但是有一个小问题,就是打包后会把这个打包好的js文件引入到临时html中,可是文件名是什么呢?其实,这个文件名,要在webpack的配置文件中规定。我的example中你可以看到我没有给一个output的filename,那么webpack会把原始文件的文件名当做输出的文件的文件名。你也可以自己规定这个文件名,但是你得更多了解webpack的配置。

等一下,你说我是不是把gulp忘得一干二净了。没错,当你在进入到测试这个层面的时候,gulp触发完karma就没它什么事了。

在webpack中使用babel编译ES6代码

现在而言,还没有哪一个环境默认原生支持全部的ES6语法,所以大部分时候,我们都用babel进行编译,其实也就是把ES6转化为ES5。但babel也有一些语法暂不能支持,而且转码为ES5后,跟ES6的原理就完全不同了,特别是import,export的模块化问题上。但是对于我们而言,目前还没有别的办法。

前面我们说webpack是可以解决require的模块化问题,但是我们要用ES6中的好用的语法糖,比如箭头函数,比如Array.isArray(),就需要babel编译后再执行。

对于熟悉webpack的同学而言,非常了解webpack配置中module.loaders这个选项的作用。我们先看下如何配置babel到webpack中。

首先,你需要安装babel-loader:

npm install --save-dev babel-loader

说到这里,前面也忘记说了,所有的gulp,karma的插件,也是要这样安装好,除非有提到说这个插件是自带的,不然都要通过npm进行安装。

安装好之后进行webpack的配置,例子

{
    module: {
        loaders: [
            {test: /\.js$/,loader: "babel?presets[]=latest"}
        ]
    }
}

这样当webpack在打包时,发现要打包的文件(包括require的文件)是.js的话,就会调用babel,让babel编译完文件之后,再作为普通文件对待进行下一步打包动作。

karma中使用webpack时获取源码的覆盖率

用webpack在测试中有一个问题,就是测试的时候只能测试到webpack打包完以后的文件,如果要查覆盖率的话就很麻烦,因为我们测试的时候根本没有覆盖带源码,而是覆盖的打包完的代码,不科学啊。

我们其实是希望覆盖率报告中告诉我们源码里面哪些是已经被覆盖到的,而不是打包完以后哪些被覆盖到了。打包完以后的覆盖率没意义啊,webpack还过了一层babel,代码跟源码完全是两回事。因此,我们必须想办法把源码的覆盖率搞出来。但是问题就非常明显了,karma的临时html中只有webpack打包完的那个js,怎么才能覆盖原始文件呢?况且,我们的原始文件,还是用ES6写的模块化文件,没法在浏览器里面执行啊。其实只需要一个插件就可以解决。

在webpack中,使用一个叫isparta-loader的插件,就可以让karma记住webpack打包时每个文件对应的代码,从而让测试代码与源码可以一一对应,最终的效果就是,在覆盖率报告中,可以看到源码的覆盖率。

在webpack的配置中加入如下配置,具体看这里

{
    module: {
        preLoaders: [
            {test: /.js$/, loader: "isparta", exclude:/node_modules|bower_components/}
        ]
    }
}

上面已经用过一次module了,这次是在module中加一个preLoaders,这个选项的作用是在所有的loeader执行之前,执行这里面包含的loader,所以在babel执行之前,isparta会执行,这样webpack打包时,源码与最终的打包结果代码之间的对应关系就可以非常好的对应,覆盖率就可以被查到。

你可以clone我的example,在本地执行之后,自己可以看下是不是可以看到源码的覆盖率。

小结

所谓实践出真知,如果你连我给的example都懒得去看,去尝试,那么可能在理解上无法真正掌握自己需要的东西。

gulp karma webpack都是工具,他们的作用从简单的角度看,就是一类带特殊功能的触发器,通过插件的形式补充新功能,触发其他工具工作。当然,webpack本身的打包功能还是比较复杂,不能把它当触发器来对待,但是它内部确实需要触发babel的转码动作。

一旦理解了他们之间的关系,你要做的,就是去熟悉每一个工具各自的配置、如何写代码来使用,如此,你就可以更加灵活的选择,甚至了解了这个背后的机制之后,可以加入更多的工具进来,给自己开发一个方便自己工作的工作流管理工具,我也正是这样探索,才开发出了componer,这个以开发components为目标的工具。

2016-12-17 | , , , ,