懒惰,是促使人类科技发展的重要因素。我们告别刀耕火种的时代,正是因为人们不断地通过发明工具和优化精简手动的流程来实现效率的提升,让人们能专注于自己专业的领域,其他的事情交给机械去处理。

而同样在前端的领域,我们也是从蛮荒的时代走向如今的繁荣发达,各类框架百花齐放,前端也不再是局限于静态页面,形态更加接近于传统应用的开发,功能的复杂度呈指数级的提升。有可能一个库要同时满足多个项目使用,版本的迭代也会导致功能的变化,如果每次到这个时候,就要让我重新测试一遍代码,那我还能不能准时下班了……

所以我们需要在开发之前,就确定这个框架/库的使用规则,通过这个规则去写我们的测试用例,这样我们开发完之后,跑一遍测试用例,让机器取代我们人工手动地去测试代码,就可以知道我们的框架在各种环境和使用情况下会否报错,而不再需要逐一项目去验证。


当然并不是只有我有这种想法,大神们早就想到了,于是测试框架应运而生。
就笔者了解,目前前端领域比较流行的单元测试框架有 mochaJasmine 等等。。

事不宜迟,我们看看如何上手测试,这里我们以mocha举例子。
首先要在项目中安装mocha

npm install mocha --save-dev

在项目的根目录创建 test 文件夹 并创建文件 index.js。现在要测试 Array.prototype.indexOf

var assert = require('assert')

describe('Array', function() {
  describe('#indexOf()', function() {
    it('should return -1 when the value is not present', function() {
      assert.equal(-1, [1, 2, 3].indexOf(4))
    })
  })
})

在命令行中执行

mocha

如果看到这样的结果,恭喜你,你的代码已经通过了测试(滑稽)

Array
    #indexOf()
      ✓ should return -1 when the value is not present


  1 passing (13ms)

以上是一个简单例子,可能有很多同学还是会一脸懵逼,说好的讲测试框架,你给引入一个assert的是什么鬼?
这我就要解释一下了,这个是nodejs中内置的断言模块

何谓断言?

在程序设计中,断言(assertion)是一种放在程序中的一阶逻辑(如一个结果为真或是假的逻辑判断式),目的是为了标示与验证程序开发者预期的结果-当程序运行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止运行,并给出错误消息。 -- 摘自维基百科

测试框架的职责,只是帮你对项目中各种各样的测试来进行一个规划,方便对测试来进行拆分整理,并在测试中添加一些描述来方便人眼判别,实际上,输入参数到函数中执行,最终输出什么结果才能通过测试,是需要我们去“告诉”测试框架的。而断言,正好满足我们这种行为的语义,也是最自然的。

一般来说使用默认的断言模块已经足以应对多种情况,当然如果你对这种语法使用表示很不爽的时候,也可以在测试框架中选择使用其他断言库, 比如 should.js , chai 这里就不再过多地赘述。

如何测试异步代码

你可以在回调中执行done

var assert = require('assert')

describe('Array', function() {
  describe('#indexOf()', function() {
    it('should return -1 when the value is not present', function(done) {
      setTimeout(function() {
        var value = 1;
        if (value) done(assert.equal(-1, [1, 2, 3].indexOf(4)))
        else done()
      }, 30)
    })
  })
})

promise

it('should xxx', function(done) {
  return new Promise(function(resolve, reject){}).then(done)
})

支持 async await

it('should return -1 when the value is not present', async function(done) {
  let a = await func();
  if (a) done()
  else done(new Error("xxx"))
})

需要注意的是,在使用mocha api的时候并不提倡 箭头函数的写法,因为这样你会拿不到当前执行的context, 当前context下内置了比如 timeOut 等等的一些api,详情请翻阅文档,这里就不做过多展开了。


测试环境

是时候在浏览器跑一下了!

没错,截止到现在,我们一直都是在nodejs的环境下执行代码,至于浏览器的执行环境有多少差异,大家不言自明,简简单单在node上面跑一下测试时不具有普适性,不可靠的,我们需要在浏览器环境都跑一遍。

什么?你想打开控制台粘帖代码执行?为了拯救你于无尽的加班测试中,是时候推荐你接入使用karma了。

karma 是一套测试执行管理工具,可以配置需要测试的浏览器环境,以及测试前的一系列准备工作等,需要时还可以接入测试覆盖率的报告生成。

为了降低同学们配置karma的门槛,这里我换用了断言库should.js
npm install should --save-dev

断言写法替换如下

// assert.equal([1,2,3].indexOf(4), -1)
[1,2,3].indexOf(4).should.equal(-1)

首先我们需要安装karma

npm install karma-cli -g
npm install karma --save-dev

进到项目中初始化karma

karma init

接下来你会收到一连串的询问

Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> mocha

Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no

Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome
>

What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> node_modules/should/should.js
> test/*.js

Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
> 

Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes

配置完成后,项目的根目录会出现一个 karma.conf.js 文件,我很想展开,但是如果继续讲下去估计讲到天亮,所以想深入了解每个配置项的作用可以翻阅karma文档

// Karma configuration
// Generated on Fri Aug 04 2017 20:53:38 GMT+0800 (CST)

module.exports = function(config) {
  let configuration = {

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',


    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha'],


    // list of files / patterns to load in the browser
    files: [
      'node_modules/should/should.js',
      'js/*.js',
      'quz/*.js',
      'test/*.js'
    ],


    // list of files to exclude
    exclude: [
    ],


    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
      'quz/*.js': 'coverage',
      'js/*.js': 'coverage'
    },


    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress', 'coverage', 'coveralls'],
    coverageReporter: {
      type : 'lcov',
      dir : 'coverage/'
    },

    // web server port
    port: 9876,
    plugins: [
      'karma-mocha',
      'karma-chrome-launcher',
      'karma-firefox-launcher',
      'karma-coverage',
      'karma-coveralls'
    ],

    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['ChromeHeadless', 'FirefoxHeadless'],

    customLaunchers: {
      Chrome_travis_ci: {
        base: "ChromeHeadless",
        flags: ["--no-sandbox"]
      },
      FirefoxHeadless: { 
        base: "Firefox", 
        flags: ["-headless"]
      }
    },

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: process.env.TRAVIS,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity
  }
  if (process.env.TRAVIS) {
    configuration.browsers = ["Chrome_travis_ci", 'FirefoxHeadless'];
  }
  config.set(configuration)
}

接下来我们跑一发

karma start

你的黑窗口会有这样的日志

02 02 2018 18:10:26.181:WARN [karma]: No captured browser, open http://localhost:9876/
02 02 2018 18:10:26.193:INFO [karma]: Karma v2.0.0 server started at http://0.0.0.0:9876/
02 02 2018 18:10:26.194:INFO [launcher]: Launching browser Chrome with unlimited concurrency
02 02 2018 18:10:26.199:INFO [launcher]: Starting browser Chrome
02 02 2018 18:10:27.346:INFO [Chrome 63.0.3239 (Mac OS X 10.13.2)]: Connected on socket lv0dmScnbh5jC2NgAAAA withid 45333497
Chrome 63.0.3239 (Mac OS X 10.13.2): Executed 1 of 1 SUCCESS (0.003 secs / 0.001 secs)

这时候你的电脑会自动开启浏览器,并且常驻在桌面,karma一旦观测到你的代码发生变动,就会重新跑一次测试用例,从此告别繁复的测试环节,专心写代码。perfect!

完了么?并没有!你还可以更懒,把当前测试流程接入CI,成为部署环境前的一个环节,当然这已经超出了本文应该讨论的范围。

长路漫漫,本文仅限于大家作为了解测试的开篇,实际上隐藏了很多的细节,真正的环境,其实碰到的问题比这个要复杂得多,但是如果懂得了测试的原理,相信聪明的你碰到再难的问题也能想到解决的办法。

学海无涯,共勉之。


elliott_hu
204 声望7 粉丝

路上自带音乐脑放的doooooooooge.