基於PhantomFlow的自動化UI測試

基於PhantomFlow的自動化UI測試

本文的目錄結構:css

  1. 自動化測試的意義
  2. 可測試方向分析
  3. 競品分析&技術選型
  4. PhantomFlow介紹
  5. 持續集成

自動化測試的意義

  • 一個項目最終會通過快速迭代走向以維護爲主的狀態,在合理的時機以合理的方式引入自動化測試能有效減小人工維護成本。
  • 自動化的收益 = 迭代次數 全手動執行成本 – 首次自動化成本 – 維護次數 維護成本
  • 另外一方面,當咱們須要對代碼進行重構或者完善,在修改結束時咱們如何肯定項目僅僅是被重構了,而不是被改寫了?此時測試將是一根救命稻草,它是一個衡量標準,告訴開發人員這麼作是否將改變結果。

可測試方向分析

前端自動化測試的方向有:html

  • 單元測試
  • UI迴歸測試
  • 功能測試
  • 性能測試
單元測試
  • 在計算機編程中,單元測試(Unit Testing)又稱爲模塊測試, 是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工做。
  • 單元測試已經有很是完善的工具體系,借用2016 JavaScript 之星的圖,經常使用的單元測試框架有:前端

UI迴歸測試

UI迴歸測試一般採用的方法是像素對比:java

  • 像素對比基本的思想認爲,若是網站沒有由於你的改動而界面錯亂,那麼在截圖上測試頁面應當跟正常頁面保持一致。
  • 像素對比比較出名的工具是PhantomCSS,它結合了 CasperJS 截圖和 ResembleJs 圖像對比分析。從易用性和對比效果來講是很不錯的。
像素對比 - PhantomCSS

初次運行的時候,會截圖並做爲baseline,後面再運行的時候,再生成截圖,並與baseline比較,生成diff結果。react

像素對比須要注意的事項:git

  • 推薦對某些區域進行測試而不是整個頁面。圖像越大對比越慢。
  • 若是測試區域內有動態元素,能夠經過選擇器來隱藏。
  • 界面對比只是一個環節,需與其餘測試相結合,合理結合纔是關鍵。
功能測試
  • 僅僅對界面進行測試是不夠的,即便界面正確,功能不正確也是斷然不能接受的。
  • 最直接的功能測試就是經過模擬用戶操做流程來判斷頁面的展示是否符合預期。
  • 有時,咱們須要瀏覽器處理網頁,但並不須要瀏覽,好比生成網頁的截圖、抓取網頁數據等操做。PhantomJS的功能,就是提供一個瀏覽器環境,你能夠把它看做一個「虛擬瀏覽器」,除了不能瀏覽,其餘與正常瀏覽器同樣。它的內核是WebKit引擎,不提供圖形界面,咱們能夠用它完成一些特殊的用途。
PhantomJS和CasperJS
  • CasperJS是對PhantomJS的封裝,提供了更加易用的API, 加強了測試等方面的支持。
  • 以下圖,很方便的實現了一個百度貼吧自動發帖的功能。github

性能測試
  • 性能測試一般來測試網站的性能,如白屏時間、首屏時間等。
  • 一般的工具備:chrome devtool,PageSpeed等在線測試網站。chrome

    考慮到咱們主題是nek-ui組件庫的測試,性能測試的部分,這裏不作贅述。npm

競品分析&技術選型

咱們的測試對象是NEK-UI組件庫,這一部分分析了其餘組件庫的測試方法並選擇了最終的測試方案。編程

RegualrUI測試方案分析:

RegularUI使用的測試方案是karma + mocha的黃金搭檔

這種方式存在的問題:

  • 沒有UI部分的測試,這也就是單元測試與UI測試的差異。
  • 雖然能夠經過調用組件的某些方法,達到用戶操做一樣的效果,可是跟真實的用戶操做仍是有差異的。好比,這個時候,template的這個方法根本沒綁定,或者傳參錯誤,這種狀況是覆蓋不到的。
Ant-design測試方案分析:
  • Ant-design是螞蟻金服的一套企業級的 UI 設計語言和 React 實現,目前是Github上一個很火的項目:

  • Ant-design做爲一個基於react的組件庫,使用的測試框架是一樣出自Facebook的Jest。

  • Ant-design使用的是Jest中稱爲snapshot testing的測試方案。

  • Jest的官方文檔上介紹到,Jest的Snapshot Testing與典型的snapshot test不一樣,不是生成截圖並比較圖片的差別,而是直接輸出React tree 的最終渲染dom結構。

    Snnpshot Testing介紹:

再來看看Ant-design中的實際使用:

測試某個組件的時候,就會引入改組件文件夾裏demo文件夾下的全部md文件,這個md文件是組件的各類示例,同時也用於ant-design的官方文檔。而後,使用enzyme和enzyme-to-json提供的方法通過render->renderToJson->toMatchSnapshot, 第一次運行的時候會輸出以下的.snap文件:

這個文件要隨着代碼一塊兒提交到倉庫,下次運行測試的時候,就和這個.snap文件作比較。

固然僅僅測試dom結構不變是不夠的,ant-design的測試裏,還有模擬用戶操做的測試。以下兩個文件,demo.test.js是上面的snapshot部分,index.test.js是模擬用戶操做部分。

Index.test.js裏作了什麼呢?

在組件上綁定事件方法,而後模擬事件,判斷方法是否被調用。

這種方式存在的問題:

  • DOM結構不變並不徹底等於樣式不變。
  • 不少相關工具都是React專用。

分析完了2個組件庫的測試方案,那麼咱們指望的測試方案應該包含什麼呢?

  • 組件庫同通常的純JS庫不用的地方,使得單純的單元測試是不夠用的,最好要包含UI測試的部分。
  • 有模擬用戶操做的部分。
  • 能方便的管理test case。

    基於此,咱們最終選擇了PhantomFlow。

PhantomFlow介紹

  • PhantomFlow是基於決策樹(decision tree)的ui test 框架,是對PhantomJS、CasperJS、PhantomCSS的包裝。

  • PhantomFlow假定若是頁面正常,那麼在相同的操做下,每次頁面所展示的應該是同樣的。基於這點,使用者只須要定義一系列的操做流程和決策分支,而後利用PhantomCSS進行截圖和圖像對比。最後將測試結果在一個可視化報表中展示出來。

這裏採用倒序的方式先來看一下PhantomFlow生成的測試報告,再介紹具體的使用:

這是PhantomFlow的母公司Huddle在他們實際的業務中使用的報告截圖:

同時PhantomFlow也提供了單獨查看某一個操做流的功能:

圖中的每一條線表明一個用戶操做流。綠色的點表示截圖對比經過,紅色的點表示截圖對比失敗,灰色的點表示這僅僅是PhantomFlow流程中的一步,並無真正的操做。
黃色的表示是一個操做,可是操做裏面並無進行截圖。咱們只要關心其中綠色的點和紅色的點。

PhantomFlow是基於決策樹的,那麼什麼是決策數呢?不必吧它想的那麼神祕,咱們能夠認爲它就是普通的流程圖。

PhantomFlow方法介紹
  • flow (string, callback):初始化一個test suite,回調函數中能夠包含step, chance 和 decision。

  • step (string, callback):一個單獨的步驟,回調函數中能夠包含PhantomCSS的截圖,CasperJs的操做事件和斷言

  • decision (object):定義一個用戶的決定,參數是一個對象,key用來描述decision的名稱,value是一個function,裏面能夠包含後續的decision, chance和step

  • chance (object):功能上同decision同樣,只是在語義上區分decision,用來描述不是用戶主動的行爲。

step對應決策樹中的矩形,表示用戶具體的某一個操做。decision和chance對應決策樹中的菱形,表示用戶的選擇。

這是用PhantomFlow描述用戶喝咖啡的一個場景:

PhantomFlow在NEK-UI組件測試中的使用
  • 以ui.select組件爲例:

PhantomFlow提供了簡單的方法來描述用戶的操做流,具體的操做使用回調函數裏的CasperJS來完成:

    function goToPage() {
        casper.thenOpen("http://localhost:9001/test/index.html", function() {
            this.echo('PageTitle: ' + this.getTitle());
            phantomCSS.turnOffAnimations();
        });
    }

    function injectModule(json) {
        casper.evaluate(function(json) {
            console.log(JSON.stringify(json));
            new NEKUI.UISelect(json).$inject('#module');
        }, json);
        casper.onConsoleMessage = function(msg) {
            console.log(msg);
        }
    }

    function goToModule() {
        casper.waitForSelector(
            '#module .u-select2',
            function success() {
                phantomCSS.screenshot('#module .u-select2');
                casper.test.pass('Should see the uiselect module' );
            },
            function timeout() {
                casper.test.fail('Should see the uiselect module');
            }
        )
    }

    function clickModule() {
        casper.click('#module .dropdown_hd');
        casper.waitForSelector(
            '#module .dropdown_bd',
            function success() {
                phantomCSS.screenshot('body');
                casper.test.pass('Should see the options of module');
            },
            function timeout() {
                casper.test.fail('Should see the options of module');
            }
        )
    }

    function selectAnOption(optionIndex) {
        casper.click('#module .m-listview li:nth-child(' + (optionIndex+1) + ')');
        phantomCSS.screenshot('body');
    }複製代碼
測試報告使用介紹:

運行測試的經常使用參數:

在npm test後帶上以下參數便可

  • report:打開瀏覽器,生成測試報告。

  • debug:輸出更多的log信息,強制切換到單線程運行。

  • earlyexit: 默認爲false,設置爲true的話,遇到第一個failure就會終止測試。

  • threads:設置多線程來運行測試,默認爲4。

經常使用CasperJS方法介紹
  • casper.thenOpen(String location[, mixed options]): 用來打開一個地址,當網頁加載完成以後,執行一個方法。

  • casper.waitForSelector(String selector[, Function then, Function onTimeout, Number timeout]):等到DOM裏有一個元素匹配選擇器,能夠傳入成功的方法和失敗的方法,和等待的毫秒數(默認5000)。

  • casper.click(String selector, [Number|String X, Number|String Y]):在匹配選擇器的第一個元素上執行一次click

  • casper.mouseEvent(String type, String selector, [Number|String X, Number|String Y]):在匹配選擇器的第一個元素上觸發鼠標事件。支持的事件有:mouseup、mousedowm、click、dblclick、mousemove、mouseover、moustout、mouseenter、mouseleave and contextmenu

  • casper.getHTML([String selector, Boolean outer]):獲取匹配選擇器裏的元素的內容。

  • casper.evaluate(Function fn[, arg1[, arg2[, …]]]):在打開的當前頁面環境下執行方法。

  • casper.test.fail(String message):添加一個fail test。

  • casper.test.pass(String message):添加一個pass test。

  • casper.test.assertEquals(mixed testValue, mixed expected[, String message]):斷言兩個值嚴格相等。

使用PhantomFlow要注意的地方
  • 數據肯定性:一樣的測試用例在組件上運行屢次,產生的結果應該相同。若是測試方法裏面包含有Date.now()這種「數據不肯定」的因素,會致使每次運行測試,頁面顯示的都不相同,這個時候能夠引入sinon,用它的stub來託管數據不肯定的方法。
  • 適當的添加斷言:截圖測試的特性決定了baseline必定要正確。假如首次運行的時候截圖就錯誤,後面的運行錯誤同樣是不會報錯的。所以須要添加一些dom取值斷言。

持續集成

  • 常常手動執行npm test?很麻煩有沒有
  • 別人項目裏的這兩個徽章怎麼來的?這兩個徽章是項目可靠度的體現。

  • 持續集成(Continuous integration,CI),一種軟件工程流程,指工程師將本身對於軟件的複本,天天集成數次到主幹上。在測試驅動開發(TDD)的作法中,一般還會搭配自動單元測試。

Travis CI

Travis-ci是一款持續集成服務,它可以很好地與Github結合,每當代碼更新時自動地觸發集成過程。Travis CI

打開Travis CI的官網,用Github帳號登陸。

選擇須要打開Travis CI服務的倉庫:

開通了服務的倉庫,每當有push代碼的時候,Travis CI就會爲咱們執行相關的操做。這裏能夠查看運行的進度和結果等。

在Github提交記錄裏也會顯示CI運行的結果。

要告訴Tracvis執行什麼,須要在咱們的項目裏添加一個.travis.yml文件,其最簡單的配置以下:

這裏指定了CI運行的語言,語言版本,哪些分支,install執行npm install, script是具體的操做部分,這裏讓CI執行 npm test。

Travis CI在執行完以後,會將結果郵件通知給用戶, 默認規則以下:

By default, email notifications are sent to the committer and the commit author when they are members of the repository, 
that is they have

 - push or admin permissions for public repositories.
 - pull, push or admin permissions for private repositories.
Emails are sent when, on the given branch:

 - a build was just broken or still is broken.
 - a previously broken build was just fixed.複製代碼

關於travis ci的生命週期等更多配置能夠查閱這裏

Coveralls 代碼覆蓋率託管平臺
  • 代碼覆蓋率一般被用來衡量測試好換的指標。Coveralls就是將測試導出的覆蓋率文件進行分析,以可視化的形式展示出來的一個工具。
  • 使用Coveralls的項目包括:React、Express、Gulp、Ant-design等等。

一樣的使用Github帳號登錄:

選擇開啓服務的倉庫:

在項目的package.json文件script裏添加一條coverage的命令, 即將istanbul等覆蓋率工具生成的lcov文件給coveralls:

在travis.yml文件的after_script中運行npm run coverage,告訴CI服務器執行這條命令:

總結

  • 自動化測試不只能有效的減小人工維護成本,同時爲代碼的維護迭代提供保障。

  • 前端自動化測試的方向有:單元測試、UI迴歸測試、功能測試、性能測試。

  • RegularUI採用karma+mocha的單元測試,ant-design使用Jest的snapshot測試與模擬用戶的功能測試相結合的方式。

  • PhantomFlow是基於決策樹的,對PhantomJS, CasperJS, PhantomCSS的包裝。以簡單的方式描述用戶操做流。並配以CasperJS的頁面操做,PhantomCSS的截圖,達到很是好的自動化測試效果。

  • 測試時要保證數據的肯定性和添加適當的斷言。

  • CI是一種好的軟件工程思想。Travis CI簡單易用,解放了開發人員手動運行測試,很是值得在項目中引入。

相關文章
相關標籤/搜索