軟件測試 | UI自動化中的分層設(shè)計
以前的設(shè)計
在過去 UI 自動化測試領(lǐng)域有一個規(guī)范的設(shè)計模式是 page object 模式。 意思是測試用例不會直接定位頁面元素, 而是把每一個頁面封裝成一個類。 在這個類中封裝頁面元素。 然后測試用例調(diào)用 page 類來操作頁面元素完成測試用例。如下圖:

但這個模式已經(jīng)誕生了差不多 20 年了。 它是以當時的前端開發(fā)模式為基礎(chǔ)進行規(guī)定的。 而 20 年間前端技術(shù)和研發(fā)模式已經(jīng)發(fā)生了很大的變化,這個模式已經(jīng)不再適用當前的 UI 自動化項目了。所以我在我的項目用將上面的模式做了一些改良
改良后的設(shè)計

組件層
當下前端框架都有組件庫的概念。 目的是封裝通用的頁面元素來讓各個頁面統(tǒng)一調(diào)用。 避免了重復開發(fā)的問題并且保持風格統(tǒng)一, 比如我之前在寫前端項目的時候也會封裝組件庫, 然后在各個頁面調(diào)用組件庫中的組件。 我拿我之前寫的前端項目舉例:



如上圖中在一個前端項目中, 會專門有一個叫 component 的目錄(第一張圖),該目錄里會封裝常用的頁面組件,比如 button,input,dropdown,link 等。 而每個組件都會編寫?yīng)毩⒌倪壿嫶a和 css 代碼(第二張圖)。 最后在頁面中通過調(diào)用組件庫的組件來完成頁面邏輯(第三張圖, 頁面中調(diào)用了第二張圖里展示的 LinkButton)。 所以我們在產(chǎn)品界面中才會發(fā)現(xiàn)很多控件長得都一樣, 連屬性都一樣, 比如 class 里的值都是一樣的。 這是因為他們其實是完完全全的同一個組件。 PS:在 CSS 中比較常用 class 來定位控件。 所以我們在控件中一般能看到各種 class 定義。 這就是現(xiàn)代前端項目的開發(fā)模式, 我們總能聽到前端研發(fā)工程師們說的組件化,其實就是這個了。 這也是為什么我們會專門抽象出一層組件層的原因。 如果研發(fā)在迭代過程中修改了組件庫, 那么會影響所有的頁面。 一般這對 UI 自動化來說是一個很大的打擊。 所以為了避免這種情況出現(xiàn),同時為了簡化頁面定位成本。 所以 UI 自動化項目中也要對應(yīng)的封裝出一個組件層來專門做元素的定位和操作。 比如我之前曾經(jīng)在產(chǎn)品中見過這樣的情況, 之前我們的 button 都是下面這個樣子的。
<button> 確定 </button>
但是突然研發(fā)修改了 button 組件庫。 于是所有的 button 就變成了下面這個樣子:
<button>
??
? ? ?<span> 確定 </span>?
</button>
在修改之前一切很美好, 我們只需要用 bytext 方法或者用一個 xpath: //button[text()=‘確定’] 就能很方便的找到這個 button 并點擊. 但某一天研發(fā)的組件庫變了, button 不在有文案了, 而是在 button 里加了個 span 的子元素放置文案。 于是在 UI 自動化項目中以前所有頁面的所有 button 定位和操作都掛了, 都需要改一遍。所以我們一般在 UI 自動化中通過增加一個組件層來解決這個問題。 如下圖:


如上圖, 在我們的測試代碼中專門有一個名字叫 component 的目錄, 里面根據(jù)控件的類型劃分了很多文件, 每個文件里定義著這個類型的控件常用的定位和操作方式。 第二張圖中我們看到的是 input 這個組件在產(chǎn)品中通用的邏輯。 比如拿 form_upload 這個控件來解釋, 在產(chǎn)品中絕大部分上傳文件的控件是由 input 組件來封裝的。 點擊上傳操作其實觸發(fā)的就是往一個屬性 type=file 的 input 元素寫入文件路徑。 所以其實所有頁面的上傳操作都可以調(diào)用這個邏輯完成。 特別需要注意一點, 組件層不僅僅是封裝元素的定位邏輯的, 也會封裝一些常用操作。 還是拿上傳控件來說, 用戶在前端是要點擊上傳這個 button 后跳出一個文件選擇框來完成上傳操作的。 但是這個邏輯在 UI 自動化中是不可行的, 因為跳出這個文件選擇框并不是瀏覽器的行為,而是當前操作系統(tǒng)的行為。 所以這個框?qū)嶋H上不受 webdriver 控制,并且不同的操作系統(tǒng)下這個框的樣子也是不一樣的。 所以在 UI 自動化中,我們只能通過給這個 input 元素直接寫入上傳的文件路徑來觸發(fā)文件操作。 但是這個 input 元素的 css 代碼中設(shè)置了 style.display=none, 把這個 input 元素設(shè)置為了隱藏, 而 webdriver 是沒辦法與隱藏元素交互的。 所以我們需要先通過嵌入一段 js 代碼, 把這個 input 修改成可見元素才能完成剩下的操作。 通過這個案例我想說明在組件層除了封裝元素的定位方式外, 也會封裝元素的操作方式。
原則上,我們所有的頁面元素的交互都需要通過調(diào)用組件層的函數(shù)來完成, 一個是加快元素定位的速度 – 調(diào)用者不再需要自己定位了, 只需要調(diào)用組件層的方法,傳遞參數(shù)即可。 第二個是通過統(tǒng)一調(diào)用, 一旦后續(xù)前端組件庫發(fā)生變化,導致元素定位方式也要改變。 我們只需要修改組件層非常少量的代碼即可。 而不是研發(fā)動了一行代碼, 測試項目這邊需要成片的邏輯修改。
頁面層
頁面層與老式的 page object 模式中的頁面層基本類似。 一個頁面封裝成一個 class, 在這個 class 里定義所有的元素和對應(yīng)的操作。 只不過這類對元素的定位工作基本都是通過調(diào)用組件層的能力來完成的。 如下圖:


可以看到,一個頁面上的所有元素都是定義在這個 class 里面的。 并且這些元素的定位都是通過組件層的方法來完成的。 我們在文件開頭就會引入這些組件庫來幫助頁面層來完成元素的定位于操作。 同時建議頁面層文件不僅僅是封裝單個元素的定位, 也可以封裝頁面里常用的操作。 我們推薦這種封裝方式, 我們應(yīng)該盡量避免在 case 中直接調(diào)用頁面元素操作。 原因有二
代碼復用, 大量 case 其實都需要調(diào)用相同的邏輯,封裝成可復用的代碼可以減少 case 編寫成本
當頁面元素出現(xiàn)變化,比如增刪元素的時候。 可只修改封裝的邏輯, 而不必大量修改 case。
組件層封裝基本控件,應(yīng)對前端組件庫的變化, 而頁面層就是對產(chǎn)品界面進行封裝, 應(yīng)對的是頁面邏輯發(fā)生的變動
業(yè)務(wù)邏輯層
在我們的代碼里, 每個模塊都有 service 文件, 這個文件就是封裝的業(yè)務(wù)邏輯的文件。 我們在頁面層上封裝的邏輯都是單頁面邏輯。 而在測試中我們往往要執(zhí)行一個很復雜的業(yè)務(wù)流程。 為了執(zhí)行這樣一個業(yè)務(wù)流程我們跳轉(zhuǎn)了很多個頁面。 所以我們有必要單獨封裝這樣一層來把業(yè)務(wù)邏輯進行封裝。 這里需要提一下, 業(yè)務(wù)邏輯層相當于頁面層的上層封裝。 封裝了完成一個業(yè)務(wù)邏輯的所有操作,比如我們假設(shè)創(chuàng)建一個工作流, 需要先填寫基本配置, 然后跳轉(zhuǎn)到高級配置頁面繼續(xù)填寫, 填寫玩后點擊確定顯示創(chuàng)建成功, 到了這里還沒結(jié)束,還需要等待這個工作流程執(zhí)行到某個狀態(tài),比如運行成功狀態(tài)。 這里面需要跳轉(zhuǎn)多個頁面,甚至還需要給定一個超時時間并輪詢工作流程的狀態(tài)。 為什么要封裝這么一層呢, 因為必然有非常多的 case 去完成這樣一個流程。
需要注意的是, 我們每個模塊都需要封裝自己的業(yè)務(wù)邏輯層。 因為一個產(chǎn)品里必然有非常多的模塊并且這些模塊之間是有依賴關(guān)系的。 比如我在之前講大數(shù)據(jù)的時候介紹過, 一個大數(shù)據(jù)平臺的產(chǎn)品中做什么事都需要先把外部數(shù)據(jù)源中的數(shù)據(jù)同步到本系統(tǒng)里對吧, 所以數(shù)據(jù)中心的測試人員就的把自己模塊的業(yè)務(wù)邏輯封裝一下給其他模塊的人用。 同樣的,模型中心模塊的人要測試得先有個模型對吧, 那用來建模的建模中心就得把自己的業(yè)務(wù)邏輯封裝好了給其他人用。 在 UI 自動化中往往就是這樣的,各個模塊之間需要互相配合,互相調(diào)用。 因為就是有很多 case 是橫跨多個模塊,鏈路非常長非常復雜的。
用例層
用例層就沒啥好說的, 就是寫 case 而已。 只不過在我的這個設(shè)計里, 用例層很輕, 大部分邏輯都已經(jīng)被上面的 3 層做完了。 用例層大多數(shù)時候再調(diào)用業(yè)務(wù)邏輯層, 少數(shù)情況調(diào)用頁面層。 所以 case 層很輕,代碼很少。 沒什么可以特別強調(diào)的