咱們來看一則小故事:javascript
一個初創公司,在早期,全部的業務界面都在一個前端工程項目中,工程裏有五個界面,一個研發加上一個測試,就能保證每次發佈的新功能穩定可靠。html
隨着公司業務蓬勃發展,當年的初創公司慢慢變得愈來愈壯大,業務界面變得愈來愈多,原來的那個前端工程項目從只有五個界面,增加到如今的幾十個界面。前端
而且,業務線也變多了。經過對業務線的劃分,甚至同一個業務線內,不一樣類型的模塊劃分,公司的業務界面總量達到上千個,分別存放在幾十個前端工程項目中。java
相對的,前端團隊的研發人員和測試人員的數量也由原來的兩我的,增加到如今的幾十人。git
這時,團隊遭遇了一個問題:每次發佈上線的功能,變得再也不穩定可靠,常常出現各類各樣的問題。github
這個狀態一直在持續,而公司也不得不持續不斷得爲這些問題形成的影響買單。web
公司開始着手調查這個問題的本質緣由。公司派出了兩位調查員:小A 和 小B。chrome
小A認爲人才資源仍是不足,須要招聘更多的人,讓每一個前端業務項目都能有足夠的人來維護,借鑑早期的團隊結構,也就是達到每一個工程五個界面,一個研發和一個測試的人員規模。數據庫
小B認爲現有前端團隊的工做方式須要調整,以適應大規模的前端工程維護與建設。並給出了兩個實際案例:npm
案例一:某個前端工程內有一百多個界面,最近用戶反饋某個界面打開後沒有內容。反饋信息到達研發團隊,研發人員調查發如今幾個月以前這個界面被其它界面的修改影響了,致使此問題。當時測試人員並無發現這個問題,由於一方面測試人員並無這個模塊全部界面的名單;另外一方面當時修改的是另外的界面,測試人員將精力放在那些被修改的界面上了。最後,研發人員修復了這個界面,並交給測試人員進行測試,測試人員測試經過後發佈上線,修復了這個問題。
案例二:某個前端工程最近沒有發佈動做,但某天忽然收到用戶反饋:建立訂單時,提示訂單建立失敗。反饋信息到達研發團隊,研發人員調查發現最近安全策略模塊上線更新了某條安全策略,新的安全策略規則過於寬泛,阻止了這位反饋用戶的訂單內容。一方面研發人員引導用戶修復了非法的訂單內容;另外一方面,研發人員調整了致使問題的安全策略規則,最終解決了這個問題。
在參考了兩位調查員的報告後,團隊作出了以下調整:
在測試環境增長自動測試,自動測試分爲兩個類型:冒煙測試(探測界面是否有內容) 和 功能測試(探測功能是否暢通)
自動測試的時間設定爲:上午10點 和 晚上6點
測試的結果以交通燈的方式展現,並掛鉤到發佈平臺:紅色燈表明測試失敗、黃色燈表明測試未完成、綠色燈表明測試經過
發佈平臺只容許綠色燈的工程項目發佈上線。
這則小故事結束了,咱們來回顧這則故事中出現的兩個問題:
專業的工程師都明白一個道理:人是很容易犯錯的,一個功能,靠人自覺或當心謹慎地來維持,無異於做繭自縛。
測試
是一個很大的話題,咱們今天來聊一下 測試驅動開發
。
提及 測試驅動開發
(Test Driven Development, TDD),相信不少讀者都並不陌生。
測試驅動開發(英語:Test-driven development,縮寫爲TDD)是一種軟件開發過程當中的應用方法,由極限編程中倡導,以其倡導先寫測試程序,而後編碼實現其功能得名。測試驅動開發始於20世紀90年代。測試驅動開發的目的是取得快速反饋並使用「illustrate the main line」方法來構建程序。
測試驅動開發是戴兩頂帽子思考的開發方式:先戴上實現功能的帽子,在測試的輔助下,快速實現其功能;再戴上重構的帽子,在測試的保護下,經過去除冗餘的代碼,提升代碼質量。測試驅動着整個開發過程:首先,驅動代碼的設計和功能的實現;其後,驅動代碼的再設計和重構。
--- 維基百科
要說在實際工做中使用 TDD 的方式進行開發,可能只有少數人會這麼作。
由於,考慮到有限的、甚至急迫的業務研發週期,相信不少讀者都會感到擔心:測試會佔用開發週期,不如直接開發功能更 「省」 時間。
若是咱們用更長遠的視角來看研發這件事情,就會發現,實際上對於直接開發功能的方式,只是將軟件工程的時間提早消費了,後期仍是得將前期 「省」 去的那部分時間還回來,甚至還要支付昂貴的利息(固然了,重視短時間利益的人並不會想那麼遠)。
測試的重要性不言而喻,咱們發現,將測試放在軟件工程的越靠前的環節中,它就越能幫助到工程自己,由於:
今天,咱們來學習一個叫作 Cypress
的測試工具,使用這個工具,來幫助咱們進行前端功能研發。
咱們按照下面的步驟逐步講解:
Cypress
Cypress
Cypress
的測試文件Cypress
測試文件輔助咱們進行測試Cypress
測試文件Cypress
cypress 是一個完整的,易用的測試框架
咱們可使用 Cypress
進行: e2e
測試、集成測試、單元測試
Cypress 官網介紹瞭如下功能特色:
Cypress
會在測試運行是拍攝快照,只需將鼠標懸停在命令日誌中的命令上,便可確切瞭解每一個步驟發生的狀況。wait
或 sleep
代碼,Cypress
會自動等待命令和斷言完成。Cypress
的架構不使用 Selenium
或 WebDriver
。讓測試更快速,一致和可靠。除了以上這些功能外,Cypress
還有以下不足之處:
Oauth2.0
受權登陸測試Cypress
由於 Cypress
的體積相對較大(接近 150mb
),因此咱們接下來經過瀏覽器下載安裝 Cypress
的過程。
您也能夠經過 npm
或 yarn
來安裝 Cypress
咱們打開 Cypress 官網,點擊官網首頁的 Download Now 連接開始下載 Cypress
。
下載完成後,咱們獲得一個名爲 cypress.zip
的壓縮包文件。
解壓縮後,咱們會獲得一個名爲 Cypress
的可執行文件。
咱們在 home
目錄下建立一個名爲 cypress_demo
的空文件夾(用於存放 cypress 測試文件),做爲 Cypress
的工做區。
mkdir $HOME/cypress_demo
複製代碼
咱們打開 Cypress
執行文件,會看到以下界面:
點擊 select manually
,手動找到並選擇咱們剛纔建立的工做區文件夾 $HOME/cypress_demo
,而後點擊 打開
。
這時,咱們會看到 Cypress
自動爲咱們在 cypress_demo
文件夾中生成了一個叫作 cypress
的文件夾和一個叫作 cypress.json
的配置文件。
在 cypress
文件夾中,有 4
個子文件夾,分別是:
fixtures
存放一些測試用例中須要用到的靜態資源,好比:數據庫模擬數據(json
格式)、圖片等信息integration
存放 Cypress 測試文件plugins
存放 Cypress 插件support
存放 Cypress 自定義命令其中,在 integration
文件夾中,Cypress
還爲咱們生成了一些測試樣例文件,方便咱們參考和學習。
咱們在 Cypress
的運行界面中能夠看到這些樣例文件:
完整目錄結構以下:
cypress_demo
├── cypress # Cypress 工做目錄
│ ├── fixtures # 存放一些測試用例中須要用到的資源,好比:數據庫模擬數據、圖片、json信息等等
│ │ └── example.json
│ ├── integration # 存放 Cypress 測試文件
│ │ └── examples # 這個文件夾中存放了 Cypress 官方提供的一些測試樣例
│ │ ├── actions.spec.js
│ │ ├── aliasing.spec.js
│ │ ├── assertions.spec.js
│ │ ├── connectors.spec.js
│ │ ├── cookies.spec.js
│ │ ├── cypress_api.spec.js
│ │ ├── files.spec.js
│ │ ├── local_storage.spec.js
│ │ ├── location.spec.js
│ │ ├── misc.spec.js
│ │ ├── navigation.spec.js
│ │ ├── network_requests.spec.js
│ │ ├── querying.spec.js
│ │ ├── spies_stubs_clocks.spec.js
│ │ ├── traversal.spec.js
│ │ ├── utilities.spec.js
│ │ ├── viewport.spec.js
│ │ ├── waiting.spec.js
│ │ └── window.spec.js
│ ├── plugins # 存放 Cypress 插件
│ │ └── index.js
│ └── support # 存放 Cypress 自定義命令
│ ├── commands.js
│ └── index.js
└── cypress.json # Cypress 配置文件
複製代碼
Cypress
接下來,咱們來配置 Cypress
,根據上一小節的內容,咱們知道,配置 Cypress
,須要經過 cypress.json
這個文件。
那麼咱們具體能在裏面作哪些配置呢?完整的配置內容,請參考官網 配置指南。
這裏,咱們先關注下面兩個配置項:
chromeWebSecurity
userAgent
chromeWebSecurity
chromeWebSecurity
決定是否開啓 chrome
瀏覽器針對同源策略和不安全的混合內容的安全策略。
它默認是開啓狀態
實際上,這給咱們的測試文件內容帶來了一些限制,完整的限制名單請參考官網 Web安全限制
這裏舉個例子,咱們在同一個測試用例中分別訪問一個主域下的資源,Cypress
容許咱們這麼作:
cy.visit('https://www.cypress.io')
cy.visit('https://docs.cypress.io') // yup all good
複製代碼
但是,一旦咱們在同一個測試用例中,分別訪問不一樣主域下的資源,Cypress
默認會阻止咱們:
cy.visit('https://apple.com')
cy.visit('https://google.com') // this will immediately error
複製代碼
爲了接近實際的測試場景,咱們在 cypress.json
中將它關閉,以方便咱們接下來的測試工做:
{
"chromeWebSecurity": false
}
複製代碼
注意:即便咱們將 chromeWebSecurity
關閉,Cypress
也依然不容許在同一個測試用例中使用 cy.visit
訪問兩個不一樣的頂級域名,詳情請參考 #944
userAgent
userAgent
決定 Cypress
訪問任何網絡資源時,來自哪一個操做系統、哪一個瀏覽器、瀏覽器版本。
這裏假設咱們平時使用微信開發者工具來開發微信內 h5 頁面應用,因此咱們將 userAgent
的值設置爲微信開發者工具:
{
"chromeWebSecurity": false,
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1 wechatdevtools/1.02.1904090 MicroMessenger/6.5.7 Language/zh_CN webview/15602362378809380 webdebugger port/39554"
}
複製代碼
至此,Cypress
的環境配置就完成了。
Cypress
的測試文件每一個測試文件,都遵循一個模式,大體以下:
Cypress
中,它叫 describe
Cypress
中,它叫 before
Cypress
中,它叫 after
Cypress
中,它叫 beforeEach
Cypress
中,它叫 afterEach
Cypress
中,它叫 it
以上這些抽象概念,在 Cypress
中以以下形式展現:
describe('測試組名稱', () => {
before(() => {
console.log(' --- 在當前 describe 中全部 it 執行前,運行一次 --- ')
})
after(() => {
console.log(' --- 在當前 describe 中全部 it 執行後,運行一次 --- ')
})
beforeEach(() => {
console.log(' --- 在當前 describe 中每一個 it 執行前,運行一次 --- ')
})
afterEach(() => {
console.log(' --- 在當前 describe 中每一個 it 執行後,運行一次 --- ')
})
it('測試用例1', () => {
// 在這裏寫測試邏輯...
})
it('測試用例2', () => {
// 在這裏寫測試邏輯...
})
})
複製代碼
在開始學習寫測試用例的內容以前,咱們先思考一下:
開發一個簡單的登錄頁面,咱們平時都是如何測試這個頁面的呢?
假設:
http://www.demo.com/login
http://www.demo.com/main
demo
password
user
pwd
submit
咱們平時會手動執行的測試步驟:
將以上步驟轉換爲 Cypress
能理解的語言,以下:
describe('登陸測試', function () {
it('成功的 case', function() {
// 打開登陸頁面
cy.visit('http://www.demo.com/login')
// 輸入用戶名
cy.get('#user').type('demo')
// 輸入密碼
cy.get('#pwd').type('password')
// 點擊提交按鈕
cy.get('#submit').click()
// 判斷是否登陸成功
cy.url().should('include', '/main')
})
})
複製代碼
在 Cypress
中:
cy.visit
cy.get
cy.type
cy.click
url
地址,使用 cy.url
cy.should
Cypress
測試文件輔助咱們進行測試咱們在文件夾 cypress_demo/cypress/integration/
下新建一個名爲 demo.js
的文件,將上一小節的代碼拷貝進去,而後保存。
接着,讓咱們回到 2.2
節中打開的 Cypress
執行文件界面(若是您已經關閉了這個界面,只須要從新打開 Cypress
執行文件便可)。
能夠看到咱們剛纔新建的 demo.js
文件了:
點擊它,會開始運行測試文件
由於咱們尚未開始寫這個登陸頁面,因此運行會出錯。
注意:
請在本地 host 文件中將 www.demo.com
指向您的開發機器 ip,或使用您本身的域名;
咱們在本篇文章中使用本地 ip: 127.0.0.1
和 www.demo.com
域名
接下來,讓咱們完成登陸頁的開發工做,這些工做不在本篇文章的範圍內。
在咱們完成登陸頁開發工做後,點擊 Cypress
界面的刷新按鈕,再次運行測試文件,能夠看到,測試已經經過了:
Cypress
測試文件在第 4
節中的例子,其實是很是簡單的
咱們實際開發中,可能須要直接測試某個受限資源(須要登陸後才能訪問),假設咱們的登陸信息都存儲在 cookie
中。
對於表單,除了簡單的文字輸入,咱們也可能須要上傳圖片(好比:上傳頭像),假設咱們要上傳的圖片名稱爲 f.png
,咱們須要提早將其拷貝到文件夾 cypress_demo/cypress/fixtures/
中。
甚至咱們可能須要爲某個前端工程內全部的頁面寫冒煙測試(smoke testing)。
在 Cypress
中,以上這些通用的功能,均可以經過自定義命令來方便得使用:
cypress_demo
├── cypress
│ └── support
│ ├── commands.js # 在這個文件裏手動添加下面的命令內容
複製代碼
咱們在 commands.js
中添加咱們須要的命令:
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
Cypress.Commands.add('uploadImage', (fileName, fileType = ' ', selector) => {
cy.get(selector).then(subject => {
cy.fixture(fileName, 'base64')
.then(Cypress.Blob.base64StringToBlob)
.then(blob => {
const el = subject[0]
const testFile = new File([blob], fileName, { type: fileType })
const dataTransfer = new DataTransfer()
dataTransfer.items.add(testFile)
el.files = dataTransfer.files
subject.trigger('change')
})
})
})
Cypress.Commands.add('login', (metaData) => {
Object.keys(metaData).forEach(key => {
cy.setCookie(key, JSON.stringify(metaData[key]))
})
})
Cypress.Commands.add('smoke', selector => {
cy.get(selector).then($app => {
let text = String.prototype.trim.call($app.text())
if (text.length) {
// 冒煙測試的標準爲: 頁面是否有文本內容
expect(text.length).to.be.gt(0)
} else {
// 或者是否有圖片
expect($app.find('img').length).to.be.gt(0)
}
})
})
複製代碼
而後,咱們就能夠在測試文件中使用這些命令了:
describe('測試組標題', () => {
before(() => {
cy.login({token:'您的token', refreshToken: '您的refreshToken'})
})
it('測試登陸後才能訪問的資源', () => {
// 在這裏寫您的測試邏輯
})
it('測試上傳圖片', () => {
cy.visit('https://www.yourdomain.com/page2')
const fileName = 'f.png'
const fileType = 'image/png'
const uploadFileSelector = 'input[type=file]'
cy.uploadImage(fileName, fileType, uploadFileSelector)
...
})
it('冒煙測試', () => {
let pages = [
'https://www.yourdomain.com/page1',
'https://www.yourdomain.com/page2',
'https://www.yourdomain.com/page2'
]
pages.forEach(page => {
cy.visit(page)
cy.wait(1000)
cy.smoke('body div[id]')
})
})
})
複製代碼
除了自定義命令外,Cypress
還支持請求攔截、截圖快照和視頻,另外,咱們還能夠經過插件來擴展 Cypress
。
以上這些功能,本篇文章再也不展開,感興趣的讀者能夠閱讀下面列出的文章。
關於請求攔截,請參考官網 請求攔截器
關於截圖快照和視頻,請參考官網 截圖快照和視頻
關於插件,請參考官網 如何寫插件
最後,讓咱們思考下面幾個問題:
Cypress
的測試文件,能夠反覆使用麼?Cypress
的測試文件麼?Cypress
讓咱們的工做變得更加輕鬆?感謝您花時間閱讀這篇文章,但願這篇文章能對您有所幫助。
水滴前端團隊招募夥伴,歡迎投遞簡歷到郵箱:fed@shuidihuzhu.com