在 KKStream 實習了半年左右(2017/07~2018/02),分享一下半年下來的心得及想法,回想當初真的是很意外的緣分。
這篇不會有太多的技術成分,主要是分享一下半年來學到的網頁自動化測試及心得,可能文章會有點長,我寫完自己也很感動⋯⋯
文章內使用 Ruby code
代表用 Ruby 寫的舊自動化測試系統, Webdriver.io
代表用webdriver.io 開發的新自動化測試系統。
實習過程記錄
簡而言之我在這半年的實習中做了兩件事情,一件是維護前一位實習生 Frank 寫的自動化測試(Ruby + Selenium),包含四種瀏覽器(Chrome、Firefox、Safari、IE);第二件是建置新的自動化測試 (Webdriver.io),目前涵蓋到兩種瀏覽器(Chrome、Firefox),以下將分成 「維護 Ruby code 架構」 、 「建置 webdriver.io 架構」 這兩個部分來論述。
維護 Ruby code 架構
剛開始,先練習照著寫好的測試計畫書來做測試練習,主要測試的環境為 Web、Android、iOS 三大平台,但實際上我們的產品還有在 WebTV、Android TV、tvOS 等等平台運作。等到比較熟悉產品的內容及運作方式之後就開始接觸了神秘的自動化測試,這也是我第一次接觸到 Ruby 語言與 Selenium Grid 這個工具,還好 Ruby 跟 Python 撰寫起來很類似,不是太難上手,也因為不太有時間讓我慢慢去學 Ruby,所以學習的方式是看到程式碼有什麼不懂,就去網路上翻資料。
網頁架構:Page Object Pattern
網頁系統的架構,我們採用的設計方式是 Page Object Pattern。
Page Object Pattern:
Within your web app’s UI there are areas that your tests interact with. A Page Object simply models these as objects within the test code. This reduces the amount of duplicated code and means that if the UI changes, the fix need only be applied in one place.
採這個設計的好處,可以讓我們減少重複的程式碼及更好維護、簡潔。
以 Videopass 網站的這個頁面( www.videopass.jp/unlimited )為例:
我們將每頁可共用的區塊拆成各自獨立的元件,實作成 Ruby 的 module,如此就不用重複維護同樣的程式碼;至於 module 跟 class 的差異就請參考這篇。以下舉例使用 Page Object Pattern 時,可以共用的 module:
- header
- item_group
- bottom
這幾個部分在別的頁面也可以使用到,因此把他們抽出來實作,再來把每一頁的 Page Object 實作出來。
自動化測試:Selenium Grid
Selenium Grid 是一個 Hub-Node 的架構,我們對 Hub 下指令,Hub 會基於我們的指令對要求的 Node 進行測試,運用 Selenium Grid 可以讓我們對遠端 Browser 下指令執行、平行化測試、集中化管理、跨平台等優點。(這邊不多作介紹,預知更詳細可以到Selenium Grid wiki上查看)
之前的實習生 Frank 有建置一個 Selenium Grid 的測試環境(如下圖),假設要測的是 Firefox 瀏覽器,透過 Jenkins 去對 Hub(架在 iMac 上)下達執行測試的指令,Hub 會再對連接上的 Node2(Windowns 10)下 request,在 Node2 上啟動 Firefox 做測試,我們就可以看到 Firefox 自動開啟並執行測試。
Jenkins 上各種瀏覽器的自動測試 jobs 狀態:
但因為 iMac 上同時也有其他裝置(iPhone、iPad)的自動化測試在運行,為了減輕 iMac 上的壓力,我們便把 Jenkins、Hub 改接到另一台 Windowns 10 機器上面:
這一切看似很美好,但可能是對 Windows 信仰不足,遇到了編碼問題:
在網路上找了各式各樣的解法還是沒辦法解決,於是決定再次改版:
到這邊,我們整個網站自動化測試的系統可以穩定地運作,不會再有太多人使用而導致測試被干擾的狀況,也沒有編碼的問題。
建置 Webdriver.io 架構
在講我建置的 Webdriver.io 架構之前,先簡單介紹一下 Webdriver.io
它是一個 End to end 的Testing Framework,由Node.JS開發,而且是以同步的方式來去執行我們的測試程式,也支援了多樣的hooks來更方便地讓我們寫測試程式,可以用來跑手機、網站的測試,像我們這邊測網站的話,它會透過 Selenium 的 RESTful API 來做溝通。
特性
Extendable:
我們可以輕易的擴充指令,像是我們想要增加一個指令就能拿到 url 及 title,我們可以運用 browser.addCommand('getUrlAndTitle')
方法來去新增(參考 addCommand.js),下面使用 browser.getUrlAndTitle()
就能一次拿到 url 及 title(參考 example.js)。
Compatible:
整合了大部分的 TDD 及 BDD 測試框架,像是本篇我們用到的 cucumber 以及常見的 mocha、Jsamine。
Feature-Rich:
支援多種 Cloud Services:
(指的是可以不用自己購買硬體的設備,直接透過網路來操作運用雲端服務提供的測試設備)
支援多種 Reporters(不同的測試結果格式):
測試請求的流程
這邊舉個流程的例子,假設我們要透過 webdriver.io 去開啟 http://example.org
這個網址,寫一段 code browser.url('http://example.org')
並執行,webdriver.io 會透過Selenium 的 RESTful API 發送請求, Selenium server 接到後會在對應的 node 開啟瀏覽器並連到 http://example.org
這個網址,接著再將結果回傳到 webdriver.io。
建置 webdriver.io + cucumber 架構
Webdriver.io 在這邊告一個段落,以下是我建置的 webdriver.io + cucumber 架構:
先看到 features 的這個資料夾, 裡面總共 13 個 features:
而 step-definitions 裡面有 4 個 steps
Feature Introduction
Every
.feature
file conventionally consists of a single feature. A line starting with the keyword Feature followed by free indented text starts a feature. A feature usually contains a list of scenarios. You can write whatever you want up until the first scenario, which starts with the word Scenario (or localized equivalent; Gherkin is localized for dozens of languages) on a new line. You can use tagging to group features and scenarios together independent of your file and directory structure.
Every scenario consists of a list of steps, which must start with one of the keywords Given, When, Then, But or And. Cucumber treats them all the same, but you shouldn’t. Here is an example:Step definitions
For each step Cucumber will look for a matching step definition. Each step definition consists of a keyword, a string or regular expression, and a block.
This step definition uses a regular expression with one match group — (\d+). (It matches any sequence of digits). Therefore, it matches the first line of the scenario. The value of each matched group gets yielded to the block as a string. You must take care to have the same number of regular expression groups and block arguments. Since block arguments are always strings, you have to do any type conversions inside the block, or use Step Argument Transforms.
When Cucumber prints the results of the running features it will underline all step arguments so that it’s easier to see what part of a step was actually recognised as an argument. It will also print the path and line of the matching step definition. This makes it easy to go from a feature file to any step definition.
簡單來說,Feature 為模仿使用者操作的我們網站功能的流程,而 Steps 為描述一個步驟。
再來看到 pages 資料夾,裡面是我們 Page Object Pattern 與 module 的程式碼:
這邊用網站中的其中一頁來舉例:
對應的架構是:
對應的程式碼就是:
再來介紹最後一個比較重要的資料夾, lib/web_element_table
,裡面放著網頁上面 DOM 物件的 xpath,用來取得該 DOM 物件。
以上就是建置 webdriver.io 整體大架構的設計,忙了我一個多月 XD。
實際 run 一次測試:
最後來秀一下我建置的成果:
完成了 Chrome、Firefox 的自動測試 job:
讓測試執行的更穩定、更有效率:
最後的雷
再來的最後兩個月就是在穩定、維護舊跟新的這兩個系統,然而這幾個月下來也有採到一些雷,最後再做這一個分享:
瀏覽器 driver
每一家瀏覽器 driver 實作的操作不太一樣,基本上都會發摟 W3C Webdriver 所制定的標準走,但是這就造成有一些 webdriver.io 的 API 無法在特定的瀏覽器上運行,像是我們想要用滑鼠拖拉物件,使用 webdriverio 的 API: browser.dragAndDrop()
在 Chrome 上就可以順利拖拉,在 Firefox 、 Safari 上就會說 moveTo()
這個功能無法執行。為了要讓每一個瀏覽器都可以順利跑我們測試,沒有實作的功能我們只能用 browser.execute()
裡面塞入前端網頁可以跑的 JavaScript Code 去讓它執行。
認證
再來一個是我們測試的網站有使用 HTTP Authentication,但是在輸入帳號密碼這一步在 Safari 上無法透過 Selenium 去執行,所以 Safari 有好一段時間無法被測試⋯⋯最後的解法是跟 RD、PO 溝通,把 HTTP Authentication 拿掉,改成用 IP 白名單的方式,去測試我們的網站。
後記
如果想要這個實習機會的話可以看看 KKStream 的官網,或是 KKBOX 的 104 頁面,另外我也有分享一些面試心得在這裡。
最後我想要謝謝在這半年來照顧我 QA team 的大家,其中要特別感謝 Jersey、Louis、Paul 三位大大,沒有你們就不會有這半年來美好的回憶,謝謝!