在2018年里关于测试JavaScript的回顾

这篇文章的目的是想要让你对于在2018中测试Javascript最重要的缘由、时期和工具还有方法保持了解。这篇文章的内容参考了许多文章(连接列在要文底下),还包含了咱们本身多年在Welldone Software Solutions里不一样产品中的实现的不一样测试方案的经验。

阅读这篇文章的人,咱们假设他们只知道在2018年的前端开发社区是怎么样测试Javascript的。这是他们分享给他们的同事、家人还有朋友的很大缘由。javascript

  • 我对这篇文章作了大量的调查,若是你发现任何错误,请在下面评论,我会第一时间纠正它。
  • 留意文章底下的连接,阅读它们会让你对整个大局有所了解并使你成功这方面的专家(理论上)。
  • 最好的实践这篇文章的方法是选择一个你所须要的测试类型,再选择若干看起来适合你的工具,而后再测试他们。咱们正处于一个有大量工具和实践的环境。你的目标就是筛选它们而后对你的独立案例做最好的组合。

介绍

看一下Facebook的测试框架Jest的Logo:前端

正如你所看到的,它的口号是保证它是一个“不痛苦的”JavaScript测试框架,不过正若有些人在评论区中指出:java

(没有什么测试是不痛苦的)git

的确,Facebook使用这个口号有一个很重要的缘由。一般JavaScript开发者对网站测试是很是痛苦的,JS测试老是受限制、难以实现,速度慢和有时很是昂贵。github

无论怎么样,在对的策略和在对的工具组合下,一个几乎全覆盖的测试是能够实现的,并且成这个测试是很是容易管理、简单并且相对较快。编程

测试类型

你能够在这里这里还有这里更深刻的了解不一样的测试类型。一般状况下,对于网站来讲最重要的测试类型是:json

  • 单元测试:提供一些输入数据来测试每一个独立的函数或者类,而且保证他们的输出是符合预期的。
  • 集成测试:测试流程或者组件的表现是符合预期的,包含反作用。
  • UI测试:(A.K.A 功能测试)在无论内部结构实现的状况下,经过控制浏览器或者网站,测试产品自己的流程是否能够达到预期表现。

测试工具类型

测试工具能够分为如下几类。有一些工具仅仅只提供一个功能,有一些则提供了一个包含许多功能的工具包。redux

为了后期能够实现更复杂的功能,咱们经常会使用工具包,哪怕咱们一开始只是使用他其中的一个功能。api

  1. 提供测试结构(Testing structure)(Mocha, Jasmine, Jest, Cucumber)
  2. 提供断言函数(Assertion functions)(Chai, Jasmine, Jest, Unexpected)
  3. 建立、显示和监听测试结果(Mocha, Jasmine, Jest, Karma)
  4. 建立和对比组件和数据结构的快照,从而保证对比上一次运行的改变是符合预期的(Jest, Ava)
  5. 提供mocks, spies, and stubs (Sinon, Jasmine, enzyme, Jest, testdouble)
  6. 建立代码覆盖率报告(Istanbul, Jest, Blanket)
  7. 提供能够执行用户行为的浏览器环境或类浏览器环境 (Protractor, Nightwatch, Phantom, Casper)

让咱们来解释一下上面提到的一些东西:promise

测试结构(Testing structure)是指你的测试行为。一般测试都是运行在行为驱动开发的BDD的模式下的。他一般看起来像这样子:

describe('calculator', function() {
  // describes a module with nested "describe" functions
  describe('add', function() {
    // specify the expected behavior
    it('should add 2 numbers', function() {
       //Use assertion functions to test the expected behavior
       ...  
    })
  })
})
复制代码

断言函数(Assertion functions)是指保证测试变量通过断言函数后会返回指望值。他一般看起来像这样,最多见的是第一次个和第二个例子:

// Chai expect (popular)
expect(foo).to.be.a('string')
expect(foo).to.equal('bar')

// Jasmine expect (popular)
expect(foo).toBeString()
expect(foo).toEqual('bar')

// Chai assert
assert.typeOf(foo, 'string')
assert.equal(foo, 'bar')

// Unexpected expect
expect(foo, 'to be a', 'string')
expect(foo, 'to be', 'bar')
复制代码

提示:这里有一篇很棒的文章讲解了关于Jasmine的高级断言。

Spies为咱们提供了关于函数的信息——好比说,这个函数被调用了多少次,在什么状况下调用,被谁调用等等。

他们一般用在集成测试里,保证含有反作用的流程能够被正常测试出指望结果。这个例子,这个计算函数在某些流程里被调用了多少次?

it('should call method once with the argument 3', () => {
  
  // create a sinon spy to spy on object.method
  const spy = sinon.spy(object, 'method')
  
  // call the method with the argument "3"
  object.method(3)

  // make sure the object.method was called once, with the right arguments
  assert(spy.withArgs(3).calledOnce)
  
})
复制代码

Stubbing or dubbing把某些函数替换成为特定的函数,在这个特定函数的做用下保证行为是正常表现的。

// Sinon
sinon.stub(user, 'isValid').returns(true)

// Jasmine stubs are actually spies with stubbing functionallity
spyOn(user, 'isValid').andReturns(true)
复制代码

promises的状况下会像这样:

it('resolves with the right name', done => {
  
  // make sure User.fetch "responds" with our own value "David"
  const stub = sinon
    .stub(User.prototype, 'fetch')
    .resolves({ name: 'David' })
  
  User.fetch()
    .then(user => {
      expect(user.name).toBe('David')
      done()
    })
})
复制代码

Mocks or Fakes 模拟一些特定模块或行为来测试不一样状况下的流程。

据个例子,在测试中,Sinon能够经过模拟一个服务接口来保证在离线的状况下能够获得快速的指望响应。

it('returns an object containing all users', done => {
  
  // create and configure the fake server to replace the native network call
  const server = sinon.createFakeServer()
  server.respondWith('GET', '/users', [
    200,
    { 'Content-Type': 'application/json' },
    '[{ "id": 1, "name": "Gwen" }, { "id": 2, "name": "John" }]'
  ])

  // call a process that includes the network request that we mocked
  Users.all()
    .done(collection => {
      const expectedCollection = [
        { id: 1, name: 'Gwen' },
        { id: 2, name: 'John' }
      ]
      expect(collection.toJSON()).to.eql(expectedCollection)
      done()
    })
  
  // respond to the request
  server.respond()
  
  // remove the fake server
  server.restore()
})
复制代码

快照测试是指你拿的一个数据和另外一个指望的数据进行对比。

下面的例子来源于Jest的官方文档,他展现了的一个link组件的快照测试。

it('renders correctly', () => {
  
  // create an instance of the Link component with page and child text
  const linkInstance = (
    <Link page="http://www.facebook.com">Facebook</Link>
  )
  
  // create a data snapshot of the component
  const tree = renderer.create(linkInstance).toJSON()
  
  // compare the sata to the last snapshot
  expect(tree).toMatchSnapshot()
})
复制代码

他不会为这个组件进行渲染而且保存成一张图片,可是它能够把它的内部结构保存在一个单独的文件中,像这样子:

exports[`renders correctly 1`] = `
<a
  className="normal"
  href="http://www.facebook.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  Facebook
</a>
`;
复制代码

当测试运行时,而且有一个不一样于上一次的快照,开发者能够及时的知道它们之间不一样的地方。

注意:快照一般是用来对比组件的结构数据,可是他们也能够用来对比其余类型的数据,像redux stores 和应用中不一样单元的内部结构。

浏览器或类浏览器环境能够是它三个中的其中一个:

  • jsdom——你的纯JavaScript的环境来模拟真实的浏览器。他没有界面并不渲染任何东西。它提供window、document,body,location,cookies,selectors和你在浏览器中运行JS时你想用到的任何东西。
  • 无头浏览器环境——一个浏览器在没有界面的状况下运行,目的是为了让浏览器的响应更快。
  • 真实的浏览器环境——打开一个真实的浏览器并运行你的测试

把全部东西组合在一块儿

咱们建议尽量地用同一套工具来执行全部的测试类型:相同的测试结构和语法,断言函数,测试报告,监听机制。

咱们一样建议使用两个不一样的测试流程。一个是单元和集成测试,另外一个是UI测试。由于UI测试会耗费大量的时间,特别是测试不一样的浏览器环境和不一样的设备环境上的浏览器,它会至关耗费精力,因此你应该尽量地少在首要的流程中运行它。几个例子:只有在合并新功能分支的状况下运行。

单元测试

应该覆盖应用中全部小的单元——utils,services和helpers。为全部这些单元提供简单的边缘状况下的输入,并使用断言函数来确保单元的输出是指望的。固然也要使用覆盖率报告工具来知道哪些单元是被测试覆盖的。

单元测试要尽量的使用函数工编程和纯函数的缘由之一是,你的应用越纯,你的测试就越简单。

集成测试

这种测试专一在单元测试和应用的结果,它是测试在应用许多单元是正常的,可是全部单元整全起来的流程倒是失败的状况。

集成测试(包括快照),在另外一方面,能够检测出许多由于你修改一个东西或者删除一个东西所形成的意外错误。

它也使咱们记得在现实生活中,许多缘由包括不完美的产品设计,大范围使用黑箱,不是全部单元都是纯的,不是全部单元都是能够测试等。一些单元只须要测试大流程的一部分。

集成测试能够覆盖重要的跨流程模块。相对于单元测试,你可使用spies来替换反作用,从而保证输出能够被断言。你可使用stubs来模拟和修改不是测试流程部分。

相关文章
相关标签/搜索