【cypress】5. 測試本地web應用

把蘋果v咬哭 發表於 2021-05-03

在之前的cypress介紹裡曾提到過,cypress雖然也可以測試部署好的應用,但是它最大的能力還是發揮在測試本地應用上。
本章主要內容就是關於如何測試本地web應用的概述:

  • cypress與後臺應用之間的關係。
  • 如何配置cypress使其適合我們的應用
  • 更好的繞過應用的身份驗證機制

一、啟動本地應用

在前面幾章內容中,程式碼示例都是用的官方文件的網頁進行測試的。那個環境相當於一個線上的生產環境,而且是cypress官方的,我們們除了正常訪問
啥也做不了。啟動本地應用就是啟動你自己開發的web應用,比如我本地的測試平臺的前端應用。

不過應該還會有小夥伴好奇,為什麼就不能直接用線上已經部署好的,而非要用本地的?
這裡,我概述一下官方的回答,以供參考:

  • 在日常的本地開發中,cypress是圍繞著構建、優化的工具。
  • 資料存根。
  • 最重要的還是你要有控制這個應用的能力,比如,根據需要隨時更改調整應用的一些配置之類。

不過,也不是說線上環境和本地環境,你必須二選一才行,也可以都寫測試,這也是一個實用場景。比如,大多數的測試可以在本地環境跑,然後留一些
測試可以作為冒煙測試用,可以用到部署好的環境上去。

二、訪問本地應用

之前演示用的程式碼用不上了,現在可以新建一個測試檔案home_page_spec.js

describe('The Home Page', () => {
  it('successfully loads', () => {
    cy.visit('http://localhost:8010') // 這裡換成你自己本地的url
  })
})

訪問成功。
【cypress】5. 測試本地web應用

三、配置Cypress

在Cypress專案中,其實有個配置檔案cypress.json,就在專案根目錄下,內容預設為空{}
在這裡可以根據需要來新增cypress的各種配置,比如說 測試檔案的位置、預設超時時間、環境變數、用哪個報告等等,這裡暫時不展開了。

不過現在,可以在這裡加一個baseUrl的配置,因為後續訪問的路徑都是以這個url為基礎的。這樣就可以給cys.visit()cys .request()這種命令
自動新增baseUrl字首了。

{
  "baseUrl": "http://localhost:8010"
}

現在訪問一個相對路徑試下:

describe('The Home Page', () => {
  it('successfully loads', () => {
    cy.visit('/')
  })
})

訪問成功。
【cypress】5. 測試本地web應用

到這裡,就可以開始寫你本地應用的測試了,至於怎麼寫,就還是取決不同的專案需求了。

四、Seeding data

這裡我理解為初始化資料,比如要測試一個頁面的登入,可能就得向資料庫裡插入一個使用者資料,方便使用。在之前用selenium的時候,
通常就在setup 和 teardown裡來安排初始化測試資料的準備和清理。

在cypress中,也會有一些支援做這些額外擴充的事情的方法,通常是這3種:

  • cy.exec(),可以執行系統命令。
  • cy.task(),可以通過pluginsFile來在node中執行程式碼。
  • cy.request(),可以傳送http請求。

比如下面這段程式碼,演示的就是在測試執行之前,要做一系列事情來完成資料的初始化:

describe('The Home Page', () => {
  beforeEach(() => {
    // reset and seed the database prior to every test
    cy.exec('npm run db:reset && npm run db:seed')

    // seed a post in the DB that we control from our tests
    cy.request('POST', '/test/seed/post', {
      title: 'First Post',
      authorId: 1,
      body: '...',
    })

    // seed a user in the DB that we can control from our tests
    cy.request('POST', '/test/seed/user', { name: 'Jane' })
      .its('body')
      .as('currentUser')
  })

  it('successfully loads', () => {
    // this.currentUser will now point to the response
    // body of the cy.request() that we could use
    // to log in or work with in some way

    cy.visit('/')
  })
})

這種用法其實本質上來說沒什麼錯的,但實際上每個測試都要與伺服器或者瀏覽器互動的,這難免會拖慢測試的效率。對於這個問題,cypress
提供了些更快更好的解決方案。

1. Stubbing the server

這裡就是我理解的mock了,斷開與後端服務的依賴。既然我需要跟伺服器互動才可以拿到需要的返回資料,如果能繞開互動,直接需要用啥資料就有啥資料,連後端應用都
不需要啟了,豈不美哉?關於stub內容很多,後續使用到再繼續分解。

2. 解決登入問題

在以往編寫測試的過程中,登入一直是一個比較大的問題。你只有登入了,才可以進行後續的測試活動。那麼如果我們把登入抽離出去,然後每個測試執行之前都進行一次登入操作,
理論上來講,也是可行的。

describe('The Login Page', () => {
  beforeEach(() => {
    // reset and seed the database prior to every test
    cy.exec('npm run db:reset && npm run db:seed')

    // seed a user in the DB that we can control from our tests
    // assuming it generates a random password for us
    cy.request('POST', '/test/seed/user', { username: 'jane.lane' })
      .its('body')
      .as('currentUser')
  })

  it('sets auth cookie when logging in via form submission', function () {
    // destructuring assignment of the this.currentUser object
    const { username, password } = this.currentUser

    cy.visit('/login')

    cy.get('input[name=username]').type(username)

    // {enter} causes the form to submit
    cy.get('input[name=password]').type(`${password}{enter}`)

    // we should be redirected to /dashboard
    cy.url().should('include', '/dashboard')

    // our auth cookie should be present
    cy.getCookie('your-session-cookie').should('exist')

    // UI should reflect this user being logged in
    cy.get('h1').should('contain', 'jane.lane')
  })
})

只不過這樣整個測試下來就變得非常的慢。所以,cypress呼籲不要在每次測試前使用UI登入

當然了,你正兒八經寫的測試程式碼裡肯定是測試UI的,但是如果這個測試涉及到其他前置的一些資料狀態的依賴,那麼要避免通過UI去設定。
這裡官方還舉了個購物車的例子加以說明。

假設要測試購物車的功能。要進行測試的話,得把產品新增到購物車中。那麼產品從哪裡來? 我是否要使用UI登入到管理後臺,然後建立所有的產品,包括它們的描述、類別和圖片?
如果這樣做了,那是不是所有的產品我都要訪問一遍並且加到購物車裡呢?

答案顯然是否定的,至於怎樣做最合適,還得到後續的學習中再分享。

繼續回到上面UI登入的問題,因為cypress與selenium不同,在cypress中,可以通過使用cy.request()來跳過使用UI的需要,cy.request()可以自動獲取和設定cookie,完成登入態的設定。那麼上述的用UI執行登入的程式碼就可以優化成:

describe('The Dashboard Page', () => {
  beforeEach(() => {
    // reset and seed the database prior to every test
    cy.exec('npm run db:reset && npm run db:seed')

    // seed a user in the DB that we can control from our tests
    // assuming it generates a random password for us
    cy.request('POST', '/test/seed/user', { username: 'jane.lane' })
      .its('body')
      .as('currentUser')
  })

  it('logs in programmatically without using the UI', function () {
    // destructuring assignment of the this.currentUser object
    const { username, password } = this.currentUser

    // programmatically log us in without needing the UI
    cy.request('POST', '/login', {
      username,
      password,
    })

    // now that we're logged in, we can visit
    // any kind of restricted route!
    cy.visit('/dashboard')

    // our auth cookie should be present
    cy.getCookie('your-session-cookie').should('exist')

    // UI should reflect this user being logged in
    cy.get('h1').should('contain', 'jane.lane')
  })
})

在官方看來,這個相比於selenium是一個大優點,其實我覺得也不盡然。這個優化思想是對的,不過在之前使用selenium的時候,雖然它內建的方法不支援這麼做,但是可以藉助
requests庫來迂迴解決直接像後端傳送請求的問題。