xingbofeng / xingbofeng.github.io

counterxing的博客
https://xingbofeng.github.io
175 stars 18 forks source link

Mocha简介 #6

Open xingbofeng opened 7 years ago

xingbofeng commented 7 years ago

在实习的过程中做过还算比较多的项目,但是几乎都由于时间原因没有在真正项目中写过单元测试脚本。很久之前接触过Mocha,既然测试驱动开发是一种良好的开发实践模式,自己也在个人小项目中使用过Mocha,有空写下这篇文章。

这是我在前段时间写的微信小程序音乐播放器中写的一段单元测试脚本。

// 引入断言库chai
var expect = require('chai').expect;
var should = require('chai').should();
var supertest = require('supertest');
var server = require('../server/server.js');
describe('test server/server.js', () => {
  it('server connect without err!', (done) => {
    supertest(server).post('/').send({
      musicname: '爱的故事上集'
    }).expect(200).end((err, res) => {
      should.not.exist(err);
      done();
    });
  });
  it('test musicname = 爱的故事上集', (done) => {
    supertest(server).post('/').send({
      musicname: '爱的故事上集'
    }).expect(200).end((err, res) => {
      expect(res.body).to.be.a('array');
      expect(res.body).to.have.lengthOf(9);
      expect(res.body[0]).to.be.a('object');
      expect(res.body[0].picUrl).to.equal('http://p1.music.126.net/qZ0FaEJ-SoCi4WygXYTlkw==/17818685440103600.jpg');
      done();
    });
  });
  it('test musicname = 告白气球', (done) => {
    supertest(server).post('/').send({
      musicname: '告白气球'
    }).expect(200).end((err, res) => {
      expect(res.body).to.be.a('array');
      expect(res.body).to.have.lengthOf(9);
      expect(res.body[0]).to.be.a('object');
      expect(res.body[1].singer).to.equal('王进');
      done();
    });
  });
});

Mocha简介

Mocha官方是这样介绍它的:

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.

Mocha是一种既可运行在Node.js也可运行在浏览器中的富JavaScript测试框架,并可以让您很方便地进行异步测试。它可以较为严格地测试您的JavaScript语句是否有你所期待的输出结果。

其实,使用测试驱动开发将会大大简化编写代码的思考过程,并使它变得更加简单,更加快捷。但是仅仅编写测试是远远不够的,真正重要的东西是了解编写测试的类型以及如何组织代码来符合测试的模式。这才是我们测试的真正目的。

安装方法:

$ npm install --global mocha

您也可以把它安装在您的项目中

$ npm install --save-dev mocha

简单入门:

$ npm install mocha
$ mkdir test
$ $EDITOR test/test.js # or open with your favorite editor

然后在你的编辑器写下以下代码:

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));
    });
  });
});

使用以下命令开始测试:

$ ./node_modules/mocha/bin/mocha

终端会显示以下信息:


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

  1 passing (9ms)

您可以在package.json文件中进行测试代码的相关配置:

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

然后在终端执行以下命令

$ npm test

Mocha甚至可以较为方便地进行异步代码的测试。它可以在你的异步操作完成之际调用你的回调函数从而完成测试。通过向it()添加一个名为done()的回调函数,Mocha便能自动等待异步执行完毕时再调用回调函数,从而完成测试。

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的。

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

当然,回调函数这种ES5的写法显得也是有点low吧。项目中也许你会返回一个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);
  });
});

断言库Chai

Mocha配合断言库Chai可以让我们更加方便地进行单元测试脚本的编写。

Chai是一个较为简洁的断言库,主要有以下三种方法:

chai.should();

foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.lengthOf(3);
tea.should.have.property('flavors')
  .with.lengthOf(3);
var expect = chai.expect;

expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.lengthOf(3);
expect(tea).to.have.property('flavors')
  .with.lengthOf(3);
var assert = chai.assert;

assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');
assert.lengthOf(foo, 3)
assert.property(tea, 'flavors');
assert.lengthOf(tea.flavors, 3);

用supertest模拟http请求测试后端接口

在最开始我给出的那段代码中,我便是使用了supertest模块来模拟http请求,并使用Mocha测试了我的Node.js服务接口。

主要用了是对于返回报文中状态码的判断以及相关数据的判等。

describe('test server/server.js', () => {
  it('server connect without err!', (done) => {
    supertest(server).post('/').send({
      musicname: '爱的故事上集'
    }).expect(200).end((err, res) => {
      should.not.exist(err);
      done();
    });
  });
});

用Enzyme测试React前端页面

Enzyme官方是这样描述的

Enzyme is a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components' output.

Enzyme's API is meant to be intuitive and flexible by mimicking jQuery's API for DOM manipulation and traversal.

Enzyme是一个专门为React设计的JavaScript测试框架。它能很方便地断言你的React组件

Enzyme的API通过模仿jQuery的DOM相关API,操作遍历你的DOM节点,简单快捷地测试你的React组件。

安装方法:

npm i --save-dev enzyme
npm i --save-dev react-addons-test-utils
npm i --save-dev react-dom

使用方法: 通过将你的组件渲染成真实DOM节点,通过DOM相关API进行测试操作。

Shallow Rendering里,一个组件就是一个单元!保证了子组件的相关行为并不会影响到父组件的测试!

而以下是一个Shallow Rendering的例子:

import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';

import MyComponent from './MyComponent';
import Foo from './Foo';

describe('<MyComponent />', () => {
  it('renders three <Foo /> components', () => {
    const wrapper = shallow(<MyComponent />);
    expect(wrapper.find(Foo)).to.have.length(3);
  });

  it('renders an `.icon-star`', () => {
    const wrapper = shallow(<MyComponent />);
    expect(wrapper.find('.icon-star')).to.have.length(1);
  });

  it('renders children when passed in', () => {
    const wrapper = shallow(
      <MyComponent>
        <div className="unique" />
      </MyComponent>
    );
    expect(wrapper.contains(<div className="unique" />)).to.equal(true);
  });

  it('simulates click events', () => {
    const onButtonClick = sinon.spy();
    const wrapper = shallow(
      <Foo onButtonClick={onButtonClick} />
    );
    wrapper.find('button').simulate('click');
    expect(onButtonClick).to.have.property('callCount', 1);
  });
});

更多关于Shallow Rendering API可移步官方文档。

Shallow Rendering相反是的,Full DOM Rendering是一种较为理想的方式。它会真正地将你的组件挂载到DOM容器上,所有组件都会有完全的生命周期(如componentDidMount),并且组件之间的行为将会相互影响。

而以下是一个Full DOM Rendering的例子:

import { mount } from 'enzyme';
import sinon from 'sinon';
import Foo from './Foo';

describe('<Foo />', () => {

  it('calls componentDidMount', () => {
    sinon.spy(Foo.prototype, 'componentDidMount');
    const wrapper = mount(<Foo />);
    expect(Foo.prototype.componentDidMount.calledOnce).to.equal(true);
  });

  it('allows us to set props', () => {
    const wrapper = mount(<Foo bar="baz" />);
    expect(wrapper.props().bar).to.equal("baz");
    wrapper.setProps({ bar: "foo" });
    expect(wrapper.props().bar).to.equal("foo");
  });

  it('simulates click events', () => {
    const onButtonClick = sinon.spy();
    const wrapper = mount(
      <Foo onButtonClick={onButtonClick} />
    );
    wrapper.find('button').simulate('click');
    expect(onButtonClick.calledOnce).to.equal(true);
  });

});

更多关于Full DOM Rendering可移步官方文档。

Static Rendering会将React组件的render方法调用一次,并返回静态的HTML模板。

import { render } from 'enzyme';

describe('<Foo />', () => {

  it('renders three `.foo-bar`s', () => {
    const wrapper = render(<Foo />);
    expect(wrapper.find('.foo-bar')).to.have.length(3);
  });

  it('rendered the title', () => {
    const wrapper = render(<Foo title="unique" />);
    expect(wrapper.text()).to.contain("unique");
  });

});