在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來模擬和修改不是測試流程部分。

相關文章
相關標籤/搜索