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負責,變相減輕篇寫測試程式的工作量。


2017年4月8日星期六

2016年4月29日星期五

又來超糟糕的登入設計


我想差勁UX的極致表現就是懲罰用戶,用戶每次使用都會叫苦連天、氣得血氣上湧,其中一個例子是上一篇文章中「再見了,超糟糕的登入設計」中提過的一個超糟糕的登入設計,幸好對方已經放棄這個設計,用戶不用再受苦。

但最近從Facebook上的朋友處得知,有銀行採取了相若的登入設計。


圖片於2016年4月29日截取

首先輸入密碼必須使用虚擬鍵盤,然後大家有留意到數字盤的問題嗎?竟然不是常見的排位,是會隨機改變的!

而且 ---



用戶名稱要最少3個數字,以及採用大小寫英文字母!?用戶名也是密碼嗎!?

傳送門:
  1. 電子網絡銀行服務
  2. 點擊登入,選擇電子網絡銀行服務


2015年12月5日星期六

繼續站著工作 - 自己動手做standing desk (流動版)

上一次寫standing desk有關的文章已經是2年前,本身也用了相同的時間,別以為我會因此變得能站個8小時不停,那恐怕比坐8小時所造成的傷害更大,主要都是交替姿勢,時坐時站。

身體是否有因此變得健康呢,實在很難說,減肥的作用就肯定地說沒有效果,不過腰痛的問題就機乎不會出現。曾經試過因為運動而弄傷膝頭,坐了幾天,腰就開始酸痛。頸就沒法了呢,唯有多點轉動。

在上一篇文章中提過幾張可以在香港買得到電動昇降桌,價錢都比較高,或許大家都未必願意花那個錢吧?

不過有一個好消息,Ikea的Bekant昇降桌(油壓)已經來到香港了!價錢是HKD $1890。




不過最合用的地方應該是辦公室,在家的需求沒那麼大,但公司不配合就沒辦法。

Ergotron Workfit-s可能是個比較好的選擇。




如果閣下在辦公室也是用Laptop電腦的話,這裹有一個更划算的方法:




這個是Kickstarter的籌款項目,簡單來說就是一個portable laptop stand,但高度是為站立使用而設計的。

價錢只是USD $32 (連運費$40)

筆者的行動

不過集資期而過,而且預計出貨日期是2016年6月,作為Backer之一的筆者也覺得實在是太久了。

結果在無法等待的情況下,決定自己動手做了一個:


成品!!


跟13" MBA的比較

材料:
  1. 50.9 x 38cm的大硬卡紙二塊 HKD $15
  2. 少量黑色膠紙 $0
工具:
  1. 界刀一把 
  2. 60cm 鐵尺一把
成品呎吋:
  1. 摺疊狀態:23*38cm
  2. 打開狀態:23*23*30cm
用的材料只是硬卡紙,不過已經有足夠的承托力放置13” MBA,而且雙手可以壓在MBA上,本身也很穩定。

缺點是打開時不是那麼順暢,要一點技巧,摺就很輕鬆。


優點當然是價錢以及可以度身定做,事實上筆者自制的版本的高度就比Levi8 M為高,但又比Levi8 L小巧。

至於怎樣制作呢,首先建議大家跟video用A4紙試作一次: 




意外地簡單吧?

在正式制作前要先考慮一個問題 - 尺吋,你要多高呢?

假設你要做一個高度為H,你需要4張闊度為W、長度為L的紙。

他們之間的關係依從畢式定理:

W^2 + H ^2 = L^2

W的建議大少為23cm,這是跟MBA相應的大少。




步驟:

1) 在材料上按所需要呎吋畫上剪裁線


因為邊長最少要92cm (23 * 4),要買這樣的硬卡紙並不容易,可以斬成2份,又或者4份,之後在步驟(5)時用膠紙連著便可以。

2) 用界刀把不需要的部份裁走

3) 在需要摺合的位置上用界刀輕輕地畫上一刀,方便下一個步驟

不小心界破也沒關係,可以在步驟5中救回,但呎吋偏差太大的話就請重造吧。

4) 按線段摺合卡紙,二個方向都要摺一次。




5) 用膠紙進行連接及為接口進行補強



6) 摺成這個形狀




7) 雙手放在這個位置,然後翻開


這裹有一點小技巧,很難說明,請自行多嘗試幾次。

8) 完成!




受材料所限,展開的過程沒Levit8的影片般那麼輕鬆,要依一個正確的程序,材料方面也不算輕,跟Levit8相比有所距離,而且概不防水,外表也不特別討好,果然是元祖的設計比較棒,當Levit8出貨之後我會建議大家去買一個回來更好,現在就先用手作品來湊著用吧。

就這樣無論在家又或者辦公室都有standing desk用了。!

家中其他的Standing desk組合:




放置流動設備的斜架


Laptop專用的區域

2015年11月25日星期三

再見了,超糟糕的登入設計

轉用SmarTone的服務有近一年左右吧,當初在建立了網上戶口後就跟自己說 - 「誰叫我登入SmarTone,我就跟他反面。」
 
因為那個登入系統實在太糟糕了,無法想像是什麼人、擁有怎樣的思路才可以設計出這種概不特別安全,又會充份懲罰用戶的登入介面。

今天SmarTone寄了一個電郵過來,說簡化了用戶名稱及密碼的要求:



很普通,沒什麼奇怪吧?

對,因為這才是正常的,是過去的SmarTone把東西設計得太變態了!

(1) 首先,用戶名稱跟密碼要求一樣:





這是搞什麼啊?為什麼用戶名稱要有大細楷及最少3個數字?用戶名稱本來是一種可以告知別人也無妨的東西,SmarTone當我們都是特工,連名字都不能告訢別人嗎?


(2) 虚擬鍵盤輸入?!




當時不能用實體鍵盤輸入,一定要用On-screen的虚擬鍵盤,而且你們有發現到嗎?

鍵盤上的數字不是按1-9,0這種順序擺放,而且每次輸入都會重排。

天啊!!這是什麼懲罰?

用滑鼠打虚擬鍵盤已經夠慘了,按鍵的位置還會改變。

大家記得建立帳戶時有一個慣例是什麼嗎?

就是輸入密碼二次。

用這工具輸入2次最少有3個數字的密碼?




在錯誤輸入了數次後,還會出現:






在經過無數嘗試、失控地拍了三次檯面,總算成功地建立了帳戶 -之後一年我都沒有再登入過。

SmarTone終於放棄這些懲罰用戶的措施,實在是一件令人感到高興的事 -----但我記不了自己的帳戶名稱,你可以幫我改回一個正常的名字嗎?

2015年10月10日星期六

Dualless - 10萬用戶達成紀念

自從在2012年發佈Dualless自來,經過3年多的時間,就在昨天用戶的人數終於突破10萬了!

特此一貼以示慶祝,並且想向各位支持者說一聲:「謝謝!」

記得在最初發佈時,就想著做一個實驗,就是在不費力氣宣傳的情況下,用戶數會怎樣增長呢?

在3年後達到這個數字應該不能說很好,但也不算太差吧。

不過最初獲得2千用戶時就費了近半年的時間(當時估計有一半用戶來自越南),當初就因此太過灰心而放棄實驗,雖然還有更新,但不再關心用戶數。

現在回想起來,花費的時間總算沒有白費。

p.s Dualless的source code已經由Launchpad搬了去Github.



2015年10月10日的螢幕截圖

2015年6月29日星期一

Side Project : Team Color - 在試算表上研究顏色

在做正式產品時總是想做點side project,這大概是多想法的人的宿命吧?希望大家都不會變成這樣子就好了...


雖然現在公司的成員都是香港人,但基本上以remote team的方式運作,有時候想要一個問題的答案並不是立即便知道。

你說用Instant Messenger不就行了嗎?那麼你可以在05:00立即回應嗎?你可以在跟男/女朋友獨處時也回應嗎?

實在太多老闆認為Instant messenger就代表要立即回應,殊不知只會令員工更加討厭公司的事務。

所以一個knowledge base對remote team是很重要的,有問題先在裹面找,找不到再來發問。

在我們開發時其中一樣需要記錄的是色版,一個程式在什麼地方該用什麼顏色,這是設計師的工作,但設計與開發並不一定是同步,開發者有時候要在設計師回覆前工作,而設計師在思考用色時又需要參考原來的色版,以避免每次都用不一樣的顏色,做出不一致的設計。

最初我們用Google Spreadsheet記錄,不過開發者沒有只看Hex Color便知道顏色的能耐,故要求設計師要把Cell的背景色設定到跟內容一樣,但因為色版改了很多遍,變成了壓惡性工作。


最終的解決方法是寫Add-on,在用戶輸入十六進位的顏色代碼後就會自動把背景顏色變成該顏色.

就這樣啪啪啪的輸入:

#
#F
#FF
#FFA
#FFA5
#FFA50
#FFA500 [Enter] 立即變色


然後我們把這個視為side project放了出來,並取名為 Team Color

TeamColor的功能:
  1. 輸入16位的顏色代碼後,背景顏色會自動轉成該顏色
  2. 支援3/6/8位的16進制碼,8位為ARGB碼
  3. 提供Custom Formula用來處理顏色(e.g colorRgb, colorArgb , colorSetOpacity , colorRed  , colorSetGreen etc) 詳細請看說明文件
  4. 反轉文字及背景色

可以在Google Spreadsheet上玩漸變色啊


在試用後能給我們意見回饋及星星嗎?如果想到有趣的use-case,不妨分享一下~


相關文章
  1. 擴充Google Docs的應用(1) - 在試算表中建立連結
  2. 程式師的修練場 - 建立自己的Junkcode及測試程式庫




2015年2月1日星期日

#github 我都只是fork別人的project,你fork我又何苦呢

以上標題沒什麼特別意思、動機,只是有天隨便在Facebook上說的話,怎料大家的回應都很"奇妙"(笑)




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