本文章有視頻講解錄屏,歡迎查看:Engineering-knowledge-share-e2e-test - Google 云端硬盤
Gitlab 文檔給的定義是:
End-to-end (e2e) testing is a strategy used to check whether your application works as expected across the entire software stack and architecture
E2E 測試的特征是黑盒、(大多數情況下)測試時調用瀏覽器。在 Gitlab 項目中,我們很容易區分 E2E 測試與其它測試 —— 前者都放置在 qa/qa
目錄中。
是的,Gitlab 不推薦寫(過于詳細的) E2E 測試。理由是
Gitlab 中將測試分為四類:單元測試、集成測試、系統級白盒測試和 E2E 測試。
(圖中沒有畫出系統級白盒測試,但你肯定知道它應該畫在哪)
在現存在測試用例分布中,E2E 測試的占比是非常少的:
測試等級 | 用例個數的占比 |
---|---|
E2E 測試 | 0.17% |
系統白盒測試 | 10.9% |
集成測試 | 17.9% |
單元測試 | 71.0% |
(數據來源:Testing levels|GitLab)
不久前我完成了 Webhook 新功能的開發,將原來的簡單輸入框,擴展為支持三種模式下輸入框,就像這樣:
Before | After |
---|---|
![]() |
![]() |
考慮到原來已經存在 Webhook 的 E2E 測試,并且我也完成了詳細的單元測試和集成測試,那么我就不需要再寫 E2E 測試了,甚至不需要更新已有的 E2E 測試。
我會先給出一個 Hello world 的例子,然后介紹 E2E 測試獨有的三個重要知識點。
要了解更詳細的編寫規范,請查看官方文檔:End-to-end Testing。
Gitlab E2E 測試和單元測試一樣使用 Ruby RSpec 語法。(如果你對 RSpec 不熟悉,可以來這里了解一下:RSpec)
module QA
RSpec.describe 'Manage' do
describe 'Login' do
before do
Flow::Login.sign_in
end
it 'can login' do
Page::Main::Menu.perform do |menu|
expect(menu).to be_signed_in
end
end
end
end
end
如果你看得仔細,會發現熟悉的結構中其實藏著一些陌生的東西。比如
Flow::Login
是什么RSpec.describe 'Manage' do
中的 Manage
有什么特殊含義嗎?Page::Main::Menu
是什么,為什么 expect
語句要在 Page
的塊里執行不用怕,Flow
只是我們定義一些流程函數的類,它過于簡單,本文不再贅述,官方文檔值得一看:Flows in GitLab QA|GitLab。
至于 RSpec.describe 'Manage' do
和 Page
,下面來解釋。
在其他測試中,RSpec.describe
后的標題要可以是類、模塊甚至一段沒有規范的描述性字符串。但在 E2E 測試的標題沒那么自由,它應當是 DevOps 階段之一。
就像上述例子中的 Manage
,它表示測試的“登錄”功能屬于 DevOps 的第一階段 Manage。
你要仔細閱讀相關文檔,根據你要測試的內容,選擇一個合適的階段,這是完成 E2E 測試的第一步。
在已經存在 E2E 測試中,經常會出現這類代碼
Page::MergeRequest::New.perform do |merge_request|
...
end
其中 Page::MergeRequest
叫做 MergeRequest 頁面的 Page Object。
關于 Page Object 的詳細文檔,可以查看:Page objects in GitLab QA。
Page Object 的作用是,完成頁面 DOM 結構與測試用例的解耦。
“解耦”這件事太重要了,假如你不這么認為,想象一下你寫過好多個測試用例,它們在測試代碼中直接操作頁面元素(各種輸入框和按鈕),然后你就會遇到棘手的問題:
我們在前端代碼中??吹降?data-qa-selector
屬性,就是為 Page Object 服務的。Page Object 實現了各類方法,它們通過定位到 DOM 元素,實現業務上的各類功能,提供給測試用例使用。
一個典型的例子是登錄頁面
module QA
module Page
module Main
class Login < Page::Base
view 'path/of/login.haml' do
element :username_field
element :password_field
element :sign_in_button
end
def sign_in(user)
fill_element :username_field, user.ldap_username
fill_element :password_field, user.ldap_password
click_element :sign_in_button
end
end
end
end
end
代碼很簡潔,只由兩部分組成:view
塊和一些函數。
雖然 view
是一個新的用法,但你完全不用怕它,因為這里幾乎只有一個關鍵字:element
,用來對應前端代碼中“埋”好的 data-qa-selector
屬性。
小結一下如何寫一個 Page 對象:
data-qa-selector
定義元素view
塊中sign_in
在已經存在的 E2E 測試中,經常(甚至比 Page 對象更常見)會出現這類代碼
let(:project) do
Resource::Project.fabricate! do |project|
project.name = "project1"
end
end
這就是 Project 的 Resource 類 Resource::Project
在使用自己的方法 fabricate!
來創建資源。和 Rails 中的 resource
概念類似,我們也應該為測試中出現的資源抽象出一個 Resource 主體。
Resource 的主要功能是創建資源,所以按照規范,必須要實現 fabricate!
方法(當然了,這是你寫 Resource 的目的)。其他可選的方法是 api_get_path
、api_post_path
和 api_post_body
等,分別用來獲取已存在的資源和提供創建接口需要的參數。
例如
module QA
module Resource
class Project < Base
attr_accessor :name
def fabricate!
Page::Project.perform do |show|
show.go_to_new_project
end
Page::Project::New.perform do |new_project|
new_project.set_name(name)
new_project.create_project!
end
end
end
end
end
單元測試同樣沒有使用 Feature.enable
,而是使用新實現的 stub_feature_flags
。這兩個方法內部實現幾乎沒有區別,只是后者增加了一些日志,并且支持同時操作多個 feature flag。
但 E2E 測試就很不一樣了,它并沒有在 Rails 運行時上下文中,無法直接調用 Feature.enable
。E2E 實現了 Runtime::Feature.enable
方法,通過 Http 調用 Rails 的 Resuful 接口來實現相同的功能。
let
關鍵字在 E2E 測試中不那么受歡迎一些“昂貴”的操作(例如創建 Project Resource),且可以在多個測試用例中共享此資源時,我們推薦使用實例變量而不是 let
。
通常情況下,Gitlab 推薦使用 let
而不是實例變量,這通常是看上了 let
的惰性加載這個好處 —— 用幾次就調用幾次。但在 E2E 中,一些創建操作是非常耗時的,它們通常使用接口甚至 UI 來創建,這使得 let
多次加載的特性顯得尤為糟糕。