2017年8月6日星期日

QML Snapshot Testing與TDD的連㩗

到底應該怎樣做GUI的自動化測試呢?這個問題一直都困擾著我。

並不是不會做,而是怎樣做得有效率,基本流程有三個步驟,首先要模擬環境、然後模仿實際輸入,最後核對結果。

要是架構設計失誤,光是第一步的環境模擬就夠嗆了,模仿輸入及核對結果就是一項會隨test coverage要求上昇而變得更加吃力的苦力工作。

最慘的是每當UI設計修改,以前寫的test case就有可能要面臨推倒重來的困境。

在UI改動頻繁的項目,我甚至會直接放棄自動化的GUI測試,盡量簡化UI的元件,只把力氣放在程式邏輯及數據上,直至我見到Jest的Snapshot Testing

Jest - Snapshot Testing

有一種GUI的自動化測試方法會制作元件的Screenshot圖像,再跟之前的版本進行對比,若然二者不符測試就會失敗,要麼修改程式、要麼更新Screenshot。 

Jest的Snapshot Testing的概念也是一樣,但不弄screenshot圖像,而是把視覺元件轉換成一個像XML/HTML的文字描述的snapshot,然後作出比較。

這樣子就不用找地方存放圖像,可以直接把文字描述直接存放在版本控制內。



在見識到這種GUI自動化測試方法後,我就一直在想能不能拿到QML上用呢?會不會可以解決很多問題呢?

QML - Snapshot Testing

為了把Snapshot Testing帶進QML中,我試著做了這個項目。


QML Snapshot Testing
https://github.com/e-fever/snapshottesting


跟Jest的版本不一樣,文字描述並沒有使用XML的形式,改為使用像QML語法的表達方法,同時加入了GUI版本的”比較視窗”,可以提供更加詳細的資訊。




安裝及使用方法等就不在這裹談了,可以看Github的說明,這裹想寫的是有關於TDD的配合。

Test Driven Development

在Jest的FAQ提過TDD並不適合配合Snapshot Testing使用,主要問題是人手寫Snapshot file(即是那個UI的文字描述)太麻煩,Snapshot Testing的原意並不是提供設計指引,而是找出有沒有出乎意料的修改。


但QML的Snapshot並非完全跟Jest一樣,有二項很大的分別:
  1. QML版提供GUI的介面,不單提供Diff,還可以瀏灠完整版本的Snapshot 
  2. 與此同時,被截取Snapshot的UI元件依然可以運作,開發者可以手動輸入並用肉眼視察結果 

就著這二點的不同,要進行TDD式的做法並非不可能的事。

TDD的主要精神有2個 -「Write Test code first」、「Red-Green-Refactor Cycle」

我會用以下的例子說明如何利用Snapshot Testing並配合TDD的2個要求


任務:寫一個元件,最初會顯示”Ready?”,點擊後會變成”Go!"

工作一:Write (Failing) Test code first

大概是這個樣子。(程式代碼

tst_CustomItem.qml
import QtQuick 2.0
import QtTest 1.0
import SnapshotTesting 1.0

Item {
    id: root
    width: 320
    height: 240

    CustomItem {
        // Don't put this under TestCase object.
        id: customItem
        anchors.fill: parent
    }

    TestCase {
        name: "CustomItem"
        when: windowShown

        function test_CustomItem() {
            var snapshot = SnapshotTesting.capture(customItem);
            SnapshotTesting.matchStoredSnapshot("test_CustomItem_default", snapshot);

            mouseClick(customItem)

            snapshot = SnapshotTesting.capture(customItem);
            SnapshotTesting.matchStoredSnapshot("test_CustomItem_clicked", snapshot);
        }
    }

}

至於CustomItem的內容就先讓他一個預設的內容 - Item {}

這樣測試程式⋯⋯就完成了!

咦,不用測試顯示的文字嗎?

正常來說需要這個步驛,還要提供介面,告訢元件以外的人正在顯示的文字.
/// CustomItem.qml
Item {
    property alias displayText : text.text
    Text { 
        id: text
    }
}

但這個屬性本身僅僅是為測試提供服務,本身是沒有需要的。

在用了Snapshot Testing後就不再需要用到這個屬性,即管跑一跑以上的程式吧。




因為是第一次執行,並沒有儲存過任何snapshot,所以Snapshot.matchStoredSnapshot會彈一個”比較視窗"出來詢問,概然CustomItem還只是個空白的元件,當然回答”No”,測試程式回報失敗,我們寫了一個失敗的測試,第一項工作完成。

工作二:Red-Green-Refactor Cycle

現在測試程式亮紅燈,無法通過,開始寫CustomItem

import QtQuick 2.0

MouseArea {
    Text {
        id: text
        text: qsTr("Ready?")
        font.pixelSize: 28
        anchors.fill: parent
        verticalAlignment: Text.AlignVCenter
        horizontalAlignment: Text.AlignHCenter
    }
}

再跑一次測試程式,同樣地停在相同的地方,但不同的是程式的視寫中顯示了”Ready?”的字句。




經過肉眼的判斷,程式符合預期,所以按下yes.


之後程式繼續跑,模擬滑鼠輸入後,又進行了一次snapshot test,基於MouseArea沒有實現所需的功能,故此元件仍然顯示”Ready?”,選擇”No”

再做一次修改吧,這次加入所需要的功能。

import QtQuick 2.0

MouseArea {
    Text {
        id: text
        text: qsTr("Ready?")
        font.pixelSize: 28
        anchors.fill: parent
        verticalAlignment: Text.AlignVCenter
        horizontalAlignment: Text.AlignHCenter
    }

    onClicked: {
        text.text = "Go!";
    }
}

因為UI沒有修改,第一個Snapshot Test會直接通過,再次停在第二項測試。



這次沒錯了,所以按”Yes”,大功告成,可以commit push上伺服器了


任務三:UI規格修改

對開發者來說規格的修改是最為苦惱的, 改動太大或許會讓前功盡廢,其中受到影響最大的多數是自動化GUI測試。


假設現在要一項小的修改,文字要要改成大寫”READY!?” / “GO?”,所以CustomItem變成

import QtQuick 2.0

MouseArea {
    Text {
        id: text
        text: qsTr("READY?")
        font.pixelSize: 28
        anchors.fill: parent
        verticalAlignment: Text.AlignVCenter
        horizontalAlignment: Text.AlignHCenter
    }

    onClicked: {
        text.text = "GO!";
    }
}
(註:其實用font.capitalization便行,但示範就裹就不用這個方法了)

再跑一次Test Case,因為Snapshot不符的關係,比較用的UI又再次彈出來詢問,當然全部都回答”Yes”。





在就這種小改動的情況下,用上Snapshot Test的測試項目甚至不用作出任何的修改,程式會自動找出被改動過的元件,經開發者確認後便自動修新,這大大滅輕維護的成本。

結論

當QML的Snapshot Testing配合TDD使用時,有一點跟傳統的做法不同,就是不用先寫預期的條件判斷,在開發的過程中改為經肉眼判斷是否正確,一旦確認正確,SnapshotTesting便會自動記錄在案,那些超煩人判斷條件都交由SnapshotTesting負責,變相減輕篇寫測試程式的工作量。


沒有留言:

Creative Commons License
本網誌Ben Lau製作,以共享創意署名-非商業性-相同方式共享 3.0 香港 授權條款釋出。