初识前端测试3 -- mocha

mocha

在第一小结中的测试中用到了 mocha 框架,这一节就说说 mocha 框架吧。下面整理的内容主要来源于官网,如需了解更多请移步mocha 官网

mocha 是一个功能丰富的前端测试框架,mocha 既可以基于 Node.js 环境运行也可以在浏览器环境运行项目地址

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. Hosted on GitHub.

安装

安装有两种方式:1. 全局安装;2. 将 mocha 作为项目依赖模块安装。

npm install --global mocha
npm install --save-dev mocha

起步

测试脚本的写法

Mocha 的作用是运行测试脚本,首先必须学会写测试脚本。所谓"测试脚本",就是用来测试源码的脚本。下面是一个加法模块 add.js 的代码。

// add.js
function add(x, y) {
  return x + y;
}

module.exports = add;

要测试这个加法模块是否正确,就要写测试脚本。通常,测试脚本与所要测试的源码脚本同名,但是后缀名为.test.js(表示测试)或者.spec.js(表示规格)。比如,add.js 的测试脚本名字就是 add.test.js。

// add.test.js
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);
  });
});

上面这段代码,就是测试脚本,它可以独立执行。测试脚本里面应该包括一个或多个 describe 块,每个 describe 块应该包括一个或多个 it 块。

describe 块称为"测试套件"(test suite),表示一组相关的测试。它是一个函数,第一个参数是测试套件的名称("加法函数的测试"),第二个参数是一个实际执行的函数。

it 块称为"测试用例"(test case),表示一个单独的测试,是测试的最小单位。它也是一个函数,第一个参数是测试用例的名称("1 加 1 应该等于 2"),第二个参数是一个实际执行的函数。

断言库

上面的测试脚本里面,有一句断言。

expect(add(1, 1)).to.be.equal(2);

所谓"断言",就是判断源码的实际执行结果与预期结果是否一致,如果不一致就抛出一个错误。上面这句断言的意思是,调用 add(1, 1),结果应该等于 2。所有的测试用例(it 块)都应该含有一句或多句的断言。它是编写测试用例的关键。断言功能由断言库来实现,Mocha 本身不带断言库,所以必须先引入断言库。

var expect = require('chai').expect;

断言库有很多种,Mocha 并不限制使用哪一种,它允许你使用你想要的任何断言库。上面代码引入的断言库是 chai,并且指定使用它的 expect 断言风格。下面这些常见的断言库:

expect 断言的优点是很接近自然语言,下面是一些例子。

// 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect(foo).to.be.deep.equal({ bar: 'baz' });

// 布尔值为true
expect('everthing').to.be.ok;
expect(false).to.not.be.ok;

// typeof
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);

// include
expect([1, 2, 3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');

// empty
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;

// match
expect('foobar').to.match(/^foo/);

基本上,expect 断言的写法都是一样的。头部是 expect 方法,尾部是断言方法,比如 equal、a/an、ok、match 等。两者之间使用 to 或 to.be 连接。如果 expect 断言不成立,就会抛出一个错误。事实上,只要不抛出错误,测试用例就算通过。

it('1 加 1 应该等于 2', function() {});

上面的这个测试用例,内部没有任何代码,由于没有抛出了错误,所以还是会通过。

在命令行中使用 mocha

在命令行使用 mocha 则需要在全局安装:npm install mocha -g, 可以通过一些参数来测试指定的文件、指定展示结果的风格、导出测试报告等。可使用mocha --help命令查看所有参数。

在项目中使用 mocha

初始化一个 node 项目

mkdir step1 && cd step1
npm init -y
npm install --save-dev mocha

创建一个文件夹 test, 并在里面创建一个 test.js ,写入下面的内容。

下面这段代码主要是简单测试了一下数组 [1, 2, 3]indexOf() 方法。预期[1, 2, 3].indexOf(4)返回-1。

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

修改 package.json 文件。

{
  "scripts": {
    "test": "mocha"
  }
}

然后打开终端,在命令行中运行npm run test, 下面是运行结果。

image.png

mocha 基础用法

测试回调方法被多次调用

如果使用基于回调的异步测试,如果 done()被多次调用,则 Mocha 将抛出错误。这对于捕捉意外的双重回调很方便。

it('double done', function(done) {
  // Calling `done()` twice is an error
  setImmediate(done);
  setImmediate(done);
});

回调多次意外运行错误结果

image.png

异步代码检查

使用 Mocha 测试异步代码非常简单!只需在测试完成时调用回调。通过向它添加一个回调函数(通常名为 done),Mocha 会知道它应该等待这个函数被调用来完成测试。该回调接受 Error 实例(或其子类)或伪造值;其他任何事情都会导致测试失败。

describe('User', function() {
  describe('#save()', function() {
    it('should save without error', function(done) {
      var user = new User('Luna');
      user.save(function(err) {
        if (err) done(err);
        else done();
      });
    });
  });
});

为了使事情更简单,done()回调函数也接受一个 Error 实例(即新的 Error()),所以我们可以直接使用它:

describe('User', function() {
  describe('#save()', function() {
    it('should save without error', function(done) {
      var user = new User('Luna');
      user.save(done);
    });
  });
});

运行 Promise

或者,您可以不使用 done()回调,而是返回一个 Promise。如果您正在测试的 API 返回 Promise 而不是回调,这很有用:

beforeEach(function() {
  return db.clear().then(function() {
    return db.save([tobi, loki, jane]);
  });
});

describe('#find()', function() {
  it('respond with matching records', function() {
    return db.find({ type: 'User' }).should.eventually.have.length(3);
  });
});

使用 async/await

如果您的 JS 环境支持异步/等待,您也可以编写像这样的异步测试:

beforeEach(async function() {
  await db.clear();
  await db.save([tobi, loki, jane]);
});

describe('#find()', function() {
  it('responds with matching records', async function() {
    const users = await db.find({ type: 'User' });
    users.should.have.length(3);
  });
});

测试同步的代码

在测试同步代码时,省略回调,mocha 将自动继续下一次测试。

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

箭头函数

不鼓励在 mocha 中使用箭头函数。 表达式中绑定的 this,不能访问 mocha 上下文。例如,以下代码将失败:

describe('my suite', () => {
  it('my test', () => {
    // should set the timeout of this test to 1000 ms; instead will fail
    this.timeout(1000);
    assert.ok(true);
  });
});

如果你不需要使用 mocha 的上下文,表达式是可以正常运行的。但是,如果最终需要重构,结果可能会与预期有所不同。

钩子

凭借其默认的“BDD”风格界面,Mocha 提供了 before(),after(),beforeEach()和 afterEach()之前的钩子。这些应该用于设置先决条件并在测试后进行清理。

describe('hooks', function() {
  before(function() {
    // runs before all tests in this block
  });

  after(function() {
    // runs after all tests in this block
  });

  beforeEach(function() {
    // runs before each test in this block
  });

  afterEach(function() {
    // runs after each test in this block
  });

  // test cases
});

测试可以在你的钩子之前,之后或穿插出现。视情况而定,挂钩将按其定义的顺序运行;所有 before() 钩子运行(一次),然后任何 beforeEach() 钩子,测试,任何 afterEach() 钩子,以及最后的 after() 钩子(一次)。

钩子描述

任何钩子都可以通过可选的描述来调用,从而更容易查明测试中的错误。如果钩子被赋予了一个命名函数,那么如果没有提供描述,将使用该名称。

beforeEach(function() {
  // beforeEach hook
});

beforeEach(function namedFun() {
  // beforeEach:namedFun
});

beforeEach('some description', function() {
  // beforeEach:some description
});

异步钩子

所有钩子(before(), after(), beforeEach(), afterEach())都可能是同步或异步的,其行为与常规测试案例非常相似。例如下面的代码,您可能希望在每次测试之前用虚拟内容填充数据库:

describe('Connection', function() {
  var db = new Connection(),
    tobi = new User('tobi'),
    loki = new User('loki'),
    jane = new User('jane');

  // 每次测试前填充数据
  beforeEach(function(done) {
    db.clear(function(err) {
      if (err) return done(err);
      db.save([tobi, loki, jane], done);
    });
  });

  describe('#find()', function() {
    it('respond with matching records', function(done) {
      db.find({ type: 'User' }, function(err, res) {
        if (err) return done(err);
        res.should.have.length(3);
        done();
      });
    });
  });
});

root 级别的钩子

你也可以选择任何文件并添加“root”级别的钩子。例如,在所有 describe()块之外添加 beforeEach()。这里定义的回调 beforeEach()在任何测试用例之前运行,而不管它存在于哪个文件中(这是因为 Mocha 有一个隐含的 describe()块,称为“root 套件”)。

beforeEach(function() {
  console.log('before every test in every file');
});

延迟执行 root 套件

如果您需要在运行任何套件之前执行异步操作,则可能会延迟 root 套件。用--delay 标志运行 mocha。这将在全局上下文中附加一个特殊的回调函数 run():

setTimeout(function() {
  // do some setup

  describe('my suite', function() {
    // ...
  });

  run();
}, 5000);

待定的测试用例

待定的测试用例就是指的那些最终需要完成而待完成测试的用例,这些实例只有描述而没有会回调。待测试将包含在测试结果中,并标记为待处理。未决测试不被视为失败测试。如下面这种:

describe('Array', function() {
  describe('#indexOf()', function() {
    // pending test below
    it('should return -1 when the value is not present');
  });
});

运行结果

image.png

测试用例管理

当有很多测试用例。有时,我们希望只运行其中的几个,这时可以用 only 方法。describe 块和 it 块都允许调用 only 方法,表示只运行某个测试套件或测试用例。

describe('Array', function() {
  describe.only('#indexOf()', function() {
    // ...
  });
});

describe('Array', function() {
  describe('#indexOf()', function() {
    it.only('should return -1 unless present', function() {
      // ...
    });

    it('should return the index when present', function() {
      // ...
    });
  });
});

有时需要跳过一些测试用例可以使 skip 方法:

describe('Array', function() {
  describe.skip('#indexOf()', function() {
    // ...
  });
});

describe('Array', function() {
  describe('#indexOf()', function() {
    it.skip('should return -1 unless present', function() {
      // this test will not be run
    });

    it('should return the index when present', function() {
      // this test will be run
    });
  });
});

有时需要根据环境来判断是否跳过或者指定运行一些实例,可以参考下面的代码

it('should only test in the correct environment', function() {
  if (/* check test environment */) {
    // make assertions
  } else {
    this.skip();
  }
});

it('should only test in the correct environment', function() {
  if (/* check test environment */) {
    // make assertions
  } else {
    // do nothing
  }
});

before(function() {
  if (/* check test environment */) {
    // setup code
  } else {
    this.skip();
  }
});
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容