项目中多次使用karma + webpack + chai + mocha + sinon做单元测试。配置脚本都是从别人的项目中拷贝来的,对其中部分库是干什么的,以及这些库之间是如何融合在一起工作的具体机制都是一知半解。本文探索karma如何与测试框架合作, 希望能深入一点(本文记录个人学习的过程,或有谬误,请多指教。)

回归本源

我们从最原始的js测试出发,假设我写了2个函数:

// 待测试的代码
function add(x, y) {
  return x + y
}

function sub(x, y) {
  return x - y + 1 // 很明显这个函数写错了
}

我们现在想测试他们,怎么测试呢?

我们给待测试的函数设置了一些输入,然后我们观察他们是否能给出正确的输出。

// 测试用例
var testCase1 = function() {
  var sum = add(1, 3)
  if (sum === 4) {
    console.log('OK')
  } else {
    console.log('Error')
  }
}

var testCase2 = function() {
  var sum = sub(5, 2)
  if (sum === 3) {
    console.log('OK')
  } else {
    console.log('error')
  }
}

然后我们可以写一个html页面,加载待测试函数和测试用例,在添加上这2行运行测试用例的代码:

// 测试执行
testCase1()
testCase2()

最后在网页console我们观察结果:

clipboard.png

好的,我们发现我们的sub函数写错了。

这似乎很蠢,然后确实就是一个测试的过程,当然这个过程并不能用于工程,它有有很多问题:

  1. 每次测试需要自己去组合带测试的代码和测试用例
  2. 每次都需要自己打开浏览器去执行用例
  3. 测试结果没有统计功能,格式更是辣眼睛
  4. 测试用例没有组织成test suit,当然相应的setup和teardown操作也没法做
  5. 等等。。。

这些问题都可以通过我们上面提到的karma工具链来解决。

karma

这玩意是什么?官方说法: a test runner。那又是什么? 能不能说人话?按我个人理解,Test runner的主要功能就是执行测试用例并输出测试结果的一个库,也就是说Karma至少解决了上面提到问题里的1,2和3。 用过karma的人都知道: 通过安装一些库,做一些配置,karma就能与很多测试框架协作从而能让我们通过一条命令直接完成测试的执行和结果输出,那么Karma如果做到的呢?

我们改造一下上面实例的代码,创建一个前端项目: clipboard.png

内容很简单:

    // src/index.js
    // src里是源码
    function add(x, y) {
      return x + y
    }

    function sub(x, y) {
      return x - y + 1
    }


    // test/index.js
    // 测试用例代码写在这里, window.__karma__相关的代码先忽略
    var testCase1 = function() {
      var sum = add(1, 3)
      if (sum === 4) {
        window.__karma__.result({
          id: 1,
          description: '1 + 3 = 4',
          suite: ['leon'],
          log: ['OK'],
          success: true,
          skipped: false
        })
      } else {
        console.log('Error')
      }
    }

    var testCase2 = function() {
      var sum = sub(5, 2)
      if (sum === 3) {
        console.log('Success')
      } else {
        window.__karma__.result({
          id: 2,
          description: '5 - 2 = ' + sum,
          suite: ['leon'],
          log: ['OK'],
          success: false,
          skipped: false
        })
      }
    }

    window.__karma__.start = function () {
      window.__karma__.info({
        total: 2
      })

      testCase1()
      testCase2()

      window.__karma__.complete()
    }

// karma.conf.js
module.exports = function(config) {
  config.set({
    ....
    // list of files / patterns to load in the browser
    files: [
      'src/index.js',
      'test/index.js'
    ],
    ...
  })
}

现在我们的工程结构清晰了.

我们执行一下测试(npm run test)看看结果:

karma start karma.conf.js --log-level debug

clipboard.png

2个测试用例都被执行了,结果也清晰的打印在了console上了。

Karma如何加载文件的呢?

我们通过命令(npm run tdd)把测试网页跑起来, 然后打开测试的页面http://localhost:9876/debug.html 可以看到 clipboard.png

原来每次我们执行Karma start命令的时候,Karma就是自动把我们在karma.conf.jsfiles字段里的文件都加载到网页里了。

实际上File 字段的注释已经说得很清楚了: list of files / patterns to load in the browser

Karma如何和测试用例沟通的呢?

Karma如何知道测试的开始,结束,每条用例的结果的呢?Karma的文档里说得也很清楚(但是似乎不太准确)

上面贴test/index.js代码的时候我就提到了,window.__karma__相关的代码先忽略。实际上这个是karma给测试用例或者更高级一点的测试框架的通信接口:

从上面的例子中,可以很明显的体会到这些功能: 我们实现一个window.karma.start函数,以便Karma框架可以调用,在这个函数里我们首先告诉Karma我们一共有2个测试用例,然后我们再去执行这2个测试用例,最后通知karma测试结束了。在测试用例里,我们不再简单的使用cossole.log来打印结果,而是用window.__karma__.result把详细的结果通知给Karma

    // Karma框架调用
    window.__karma__.start = function () {
      // 我们有2个测试用例
      window.__karma__.info({
        total: 2
      })

      // 执行用例
      testCase1()
      testCase2()

      // 通知karma测试结束了
      window.__karma__.complete()
    }

和测试框架一起

但是之前我们写测试用例的时候没写过这些__karma__.xxx函数啊!是啊,一般我们是不会像例子里这样自己写测试用例,自己管理的,而都是用mocha这样的测试框架的。

例子:

var add = require('./add.js');
var expect = require('chai').expect;

describe('加法函数的测试', function() {
  it('1 加 1 应该等于 2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });
});

实际上在用mocha的时候,我们都是要安装一个库叫karma-mocha. 这个库就是把这些__karma__.xxx的函数都封装了一遍, 所以我们都只顾着写mocha的测试用例就行了。 看一眼这个库的说明:Adapter for Mocha testing framework. 原来是这个意思啊!!

结束语

很多时候我们都用一些现成的东西,以至于都不会去深究一些事情的本质,所以在遇到深一点层次的问题的时候都会觉得无所适从。有时候事情的本质实际上没那么复杂,我们只是确少去发掘的勇气和精力。