jest 自动化测试

来源:http://www.prospettivedarte.com 作者:计算机教程 人气:83 发布时间:2019-05-10
摘要:概述 jest 是 facebook 开源的,用来进行单元测试的框架,可以测试 javascipt 和react。 单元测试各种好处已经被说烂了,这里就不多扯了。重点要说的是,使用jest, 可以降低写单元测试的难

概述

jest 是 facebook 开源的,用来进行单元测试的框架,可以测试 javascipt 和 react。 单元测试各种好处已经被说烂了,这里就不多扯了。重点要说的是,使用 jest, 可以降低写单元测试的难度。

单元测试做得好,能够极大提高软件的质量,加快软件迭代更新的速度, 但是,单元测试也不是银弹,单元测试做得好,并不是测试框架好就行,其实单元测试做的好不好,很大程度上取决于代码写的是否易于测试。 单元测试不仅仅是测试代码,通过编写单元测试 case,也可以反过来重构已有的代码,使之更加容易测试。

jest 的零配置思路是我最喜欢的特性。

大多数开发者都知道需要写单元测试,但是不知道每个单元测试应用的主要内容以及如何做单元测试,在介绍jest测试框架前,我们先来了解下一些测试相关的概念。

安装方式

# yarn
yarn add --dev jest

# OR npm
npm install --save-dev jest

为什么需要单元测试?

  • 保证质量:随着迭代的过程,开发人员很难记清所有的功能点,功能点的新增和删除在代码改变后,进行回归测试时,依靠人工QA很容易出错遗漏。
  • 自动化:通过编写测试用例,只需要编写一次,多次运行,同样的事情不需要从头再来测一遍,很多时候QA的工作量就是这么增加的,新的版本上线,人工QA都需要所有的功能点从新测试一遍。
  • 特性文档:单元测试可以作为描述和记录代码所实现的所有需求,有时候可以作为文档来使用,了解一个项目可以通过阅读测试用例比看需求文档更清晰。
  • 驱动开发,指导设计:代码被测试的前提是代码本身的可测试性,那么要保证代码的可测试性,就需要在开发中注意API的设计,TDD将测试前移就是起到这么一个作用

测试方式

随着 node.js 的发展,javascipt 语言的发展,前端可以胜任越来越多的工作,后端很多时候反而沦为数据库的 http API 所以前端不仅仅是测试看得见的页面,还有很多看不见的逻辑需要测试。

jest 提供了非常方便的 API,可以对下面的场景方便的测试

测试类型

你可能接触过各种测试框架、大体上,最重要测试类型有:

  • 单元测试- 依靠模拟输入证实是否是期望的输出来分别的测试函数或者类。
  • 集成测试 - 测试若干模块来确保他们像预期的那样工作。
  • 功能测试- 在产品本身(例如在浏览器上)对一个场景进行操作,而不考虑内部结构以确保预期的行为。

一般函数

对于一般的函数,参考附录中的 jest Expect API 可以很容易的写测试 case 待测试文件:joinPath.js

const separator = '/'
const replace = new RegExp(separator   '{1,}', 'g')
export default (...parts) => parts.join(separator).replace(replace, separator)

测试文件的命名:joinPath.test.js 采用这种命名,jest 会自动找到这个文件来运行

import joinPath from 'utils/joinPath'

test('join path 01', () => {
  const path1 = '/a/b/c'
  const path2 = '/d/e/f'

  expect(joinPath(path1, path2)).toBe('/a/b/c/d/e/f')
})

test('join path 02', () => {
  const path1 = '/a/b/c/'
  const path2 = '/d/e/f'

  expect(joinPath(path1, path2)).toBe('/a/b/c/d/e/f')
})

test('join path 03', () => {
  const path1 = '/a/b/c/'
  const path2 = 'd/e/f'

  expect(joinPath(path1, path2)).toBe('/a/b/c/d/e/f')
})

测试工具类型

测试工具可以分为以下功能,有些提供一个功能,有些提供了一个组合。
使用工具组合是很常见的,即使你可以使用单一的工具实现同样的功能,是所有组合可以获得更灵活的功能。

  1. 测试环境(Mocha , Jasmine, Jest, Karma
  2. 测试结构 (Mocha , Jasmine, Jest, Cucumber
  3. 断言函数(Chai, Jasmine, Jest, Unexpected
  4. 生成,显示、监听测试结果(Mocha , Jasmine, JestKarma
  5. 生成,比较组件和数据结构的快照,以确保之前运行的更改是预期的。(Jest,Ava
  6. mocks。(sinon.js) 目前使用最多的mock库,将其分为spies、stub、fake XMLHttpRequest、Fake server、Fake time几种,根据不同的场景进行选择。
  7. 生成代码覆盖率报告。(Istanbul, Jest
  8. 浏览器或者类浏览器环境执行控制。(Protractor , Nightwatch, Phantom, Casper

异步函数

上面的是普通函数,对于异步函数,比如 ajax 请求,测试写法同样容易 待测试文件:utils/client.js

export const get = (url, headers = {}) => {
  return fetch(url, {
    method: 'GET',
    headers: {
      ...getHeaders(),
      ...headers
    }
  }).then(parseResponse)
}

测试文件:client.test.js

import { get } from 'utils/client'

test('fetch by get method', async () => {
  expect.assertions(1)
  // 测试使用了一个免费的在线 JSON API
  const url = 'https://jsonip.com/'

  const data = await get(url)
  const { about } = data
  expect(about).toBe('/about')
})

单元测试技术的实现原理

  1. 测试框架:判断内部是否存在异常,存在则console出对应的text信息
  2. 断言库:当actual值与expect值不一样时,就抛出异常,供外部测试框架检测到,这就是为什么有些测试框架可以自由选择断言库的原因,只要可以抛出异常,外部测试框架就可以工作。
  3. mock函数:创建一个新的函数,用这个函数来取代原来的函数,同时在这个新函数上添加一些额外的属性,例如called、calledWithArguments等信息
function describe (text, fn) {
    try {
        fn.apply(...);
    } catch(e) {
        assert(text)
    }
}
function fn () {
    while (...) {
        beforeEach();
        it(text, function () {
            assert();
        });
        afterEach();
    }
}
function it(text, fn) {
  ...
  fn(text)
  ...
}
function assert (expect, actual) {
    if (expect not equla actual ) {
        throw new Error(text);
    }
}

function fn () {
  ...
}

function spy(cb) {
  var proxy = function () {
    ...
  }
  proxy.called = false;
  proxy.returnValue = '...';
  ...
  return proxy;
}

var proxy = spy(fn); // 得到一个mock函数

测试的生命周期

jest 测试提供了一些测试的生命周期 API,可以辅助我们在每个 case 的开始和结束做一些处理。 这样,在进行一些和数据相关的测试时,可以在测试前准备一些数据,在测试后,清理测试数据。

4 个主要的生命周期函数:

  • afterAll(fn, timeout): 当前文件中的所有测试执行完成后执行 fn, 如果 fn 是 promise,jest 会等待 timeout 毫秒,默认 5000
  • afterEach(fn, timeout): 每个 test 执行完后执行 fn,timeout 含义同上
  • beforeAll(fn, timeout): 同 afterAll,不同之处在于在所有测试开始前执行
  • beforeEach(fn, timeout): 同 afterEach,不同之处在于在每个测试开始前执行
BeforeAll(() => {
  console.log('before all tests to excute !')
})

BeforeEach(() => {
  console.log('before each test !')
})

AfterAll(() => {
  console.log('after all tests to excute !')
})

AfterEach(() => {
  console.log('after each test !')
})

Test('test lifecycle 01', () => {
  expect(1   2).toBe(3)
})

Test('test lifecycle 03', () => {
  expect(2   2).toBe(4)
})

测试用例的钩子

describe块之中,提供测试用例的四个钩子:before()、after()、beforeEach()和afterEach()。它们会在指定时间执行。

describe('hooks', function() {

  before(function() {
    // 在本区块的所有测试用例之前执行
  });

  after(function() {
    // 在本区块的所有测试用例之后执行
  });

  beforeEach(function() {
  // 在本区块的每个测试用例之前执行
    this.closeFunc = sinon.stub();
    this.Modal = TestUtils.renderIntoDocument(
      <Modal title="whatever" handleClose={this.closeFunc}>
        <div className="m-content">
          <p className="m-text">Just some noddy content</p>
          <a href="#" className="other-link">Click me</a>
        </div>
      </Modal>
    );
    this.eventStub = {
      preventDefault: sinon.stub(),
      stopPropagation: sinon.stub(),
    };
  });

  afterEach(function() {
    // 在本区块的每个测试用例之后执行
  });

  // test cases

  it('should have a title', function() {
    var title = helpers.findByTag(this.Modal, 'h2');
    assert.equal(findDOMNode(title).firstChild.nodeValue, 'whatever');
  });

  it('should have child content', function() {
    var content = helpers.findByClass(this.Modal, 'm-content');
    assert.equal(findDOMNode(content).nodeName.toLowerCase(), 'div');
  });

  it('should have child paragraph', function() {
    var text = helpers.findByClass(this.Modal, 'm-text');
    assert.equal(findDOMNode(text).firstChild.nodeValue,
                 'Just some noddy content');
  });


});

mock

我自己觉得能不 mock,还是尽量不要 mock,很多时候觉得代码不好测试而使用 mock,还不如看看如何重构代码,使之不用 mock 也能测试。 对于某些函数中包含的一些用时过长,或者调用第三方库的地方,而这些地方并不是函数的主要功能, 那么,可以用 mock 来模拟,从而提高测试执行的速度。

对于上面异步函数的例子,我们改造成 mock 的方式:

const funcUseGet = async url => {
  return await get(url)
}

test('mock fetch get method', async () => {
  const client = require('utils/client')
  client.get = jest.fn(url => ({ mock: 'test' }))

  const url = 'http://mock.test'
  const data = await funcUseGet(url)

  expect(data).toEqual({ mock: 'test' })
})

如何写单元测试用例

react 测试

jest 可以测试 react component,但是我们用了状态分离的方式开发前端, 大部分功能都在 action 中,这部分测试基本没有做。

一些好的建议:

  • 只考虑测试,不考虑内部实现
  • 不要做无谓的断言
  • 让每个单元测试保持独立
  • 所有的方法都应该写单元测试
  • 充分考虑数据的边界条件
  • 对重点、复杂、核心代码,重点测试
  • 利用AOP(beforeEach、afterEach),减少测试代码数量,避免无用功能
  • 使用最合适的断言方式

TDD
一句话简单来说,就是先写测试,后写功能实现。TDD的目的是通过测试用例来指引实际的功能开发,让开发人员首先站在全局的视角来看待需求。具体定义可以查看维基;

BDD
行为驱动开发要求更多人员参与到软件的开发中来,鼓励开发者、QA、相关业务人员相互协作。BDD是由商业价值来驱动,通过用户接口(例如GUI)理解应用程序。详见维基.
<blockquote>


附录

Jest介绍--Painless JavaScript Testing

Jest 是一款 Facebook 开源的 JS 单元测试框架,目前 Jest 已经在 Facebook 开源的 React, React Native 等前端项目中被做为标配测试框架。

本文由皇牌天下投注网发布于计算机教程,转载请注明出处:jest 自动化测试

关键词:

上一篇:没有了

下一篇:没有了

频道精选

最火资讯