l

2019年2月20日 星期三

透過測試涵蓋率讓重構更有信心(下):特徵測試

Feb. 19 12:59~15:13

▲不管黑貓白貓,能陪睡的就是好貓XD


重構既存系統的難題

前兩集〈透過測試涵蓋率讓重構更有信心(上):分支涵蓋率〉與〈透過測試涵蓋率讓重構更有信心(中):突變測試〉所使用的範例Account類別的withdraw函數,其程式邏輯很簡單,藉由觀看程式碼很容易就可以設計出足夠的測試案例,以支持後續的重構活動。

在實務上,鄉民們經常要對既存系統(legacy system)進行重構。這些既存系統除了沒有自動化測試案例,通常程式都寫得很亂,且缺乏文件,邏輯複雜導致沒人看得懂程式在做什麼。因此,重構既存系統成為一件很困難的工作。因為沒人看得懂程式邏輯,別說重構,連要幫程式補寫測試案例都十分困難

***

特徵測試

有一種測試技巧稱為特徵測試(Characterization Test,又叫做Golden Master Test),非常適合幫既存系統設計測試案例。

特徵測試的想法很簡單:把既存系統當作一個黑盒子,既然沒人看得懂既存系統的程式邏輯,那就幹脆不要試圖去了解它的內部邏輯。只要想辦法把既存系統的實際外部行為(系統執行結果)記錄下來,這份紀錄下來的資料稱作golden master。接下來就可以去重構系統,每次重構之後重新執行一次程式,把執行結果跟原本錄製下來的golden master比對。如果比對結果一樣,就表示此次重構沒有破壞系統原本的行為。

▼特徵測試與golden master。

***

範例介紹

Teddy找了〈Gilded Rose Kata〉當作既存系統範例,待測程式有兩個類別,分別是Item與GildedRose。

▼Item類別很簡單,沒什麼程式邏輯,就是一個簡單的data class(存放資料的類別)。


▼真正棘手的是GildedRose類別,它只有一個updateQuality函數,但是這個程式邏輯真的是很難了解它的明白。


▼這個範例有一個測試案例如下。很顯然光是只有這個測試案例遠遠不足以讓開發人員有信心去重構程式,但就算是把待測程式原始碼讀了好幾回,依然不知道該怎麼設計其他測試案例。

***

Approval Tests工具簡介

接下來的問題是要怎麼產生足夠的特徵測試,以便得到一份足以支持後續重構活動的golden master。在這裡Teddy要使用Approval Tests工具,它使用資料驅動測試案例(data-driven tests,又稱為參數化測試案例)技巧,將每次測試結果全部紀錄下來當作golden master。只要提供的資料(參數)夠多,測試案例的涵蓋率越高,開發人員對於所產生的golden master信心也越高,便可開始進行重構活動。

▼首先打開〈Gilded Rose Kata〉Java版本的程式範例,將下列相依性加入pom.xml檔案中。


▼接著在updateQuality測試案例中加入Approvals.verify(app.items[0].name),這一行可以用來取代JUnit的assertEqulas。


▼執行測試案例,結果居然是錯的?!


▼打開測試案例目錄,發現增加了兩個檔案:

  • GildedRoseTest.updateQuality.approved.txt
  • GildedRoseTest.updateQuality.received.txt


*approved.txt檔案相當於golden master,*received.txt則是此次執行測試案例所得到的程式輸出結果。Approvals會自動比對這兩個檔案的內容,如果內容相同表示程式的行為沒有被改變,測試案例通過,反之則不通過。*approved.txt檔案如果不存在,Approvals會自動產生一份內容為空白的檔案。


▼把*received.txt(左邊)與*approved.txt(右邊)檔案打開,可以發現左邊的內容為foo,右邊是空白,比對失敗,所以Approvals測試案例執行失敗。


▼要讓Approvals測試成功很簡單,只要把*received.txt改名成*approved.txt。


▼再執行一次測試案例即可。

***

產生Golden Master

Approvals工具有一個CombinationApprovals類別,它提供verifyAllCombinations函數,輸入一個待測程式的函數(closure),以及若干個參數,便可以撰寫資料驅動測試案例,然後將所有測試結果記錄下來。


▼先把原本的測試案例用extract method重構一下,獨立成doUpdateQuality method,以便等一下將它傳給CombinationApprovals


▼原本的updateQulaity測試案例變成這樣:

  • 提供給verifyAllCombinations函數的第一個參數是this::doUpdateQuality,也就是剛剛重構後的測試案例。
  • 因為執行doUpdateQuality需要三個參數,分別是name、sellin、quality,所以接著提供Stirng []、Integer []、Interger [] 給verifyAllCombinations函數。
  • 目前只提供一組資料{”foo”, –1. 0},隨著提供的測試資料越多,Golden Master的內容就越完整。


▼只有一組資料{”foo”, –1. 0}產生的*received.txt檔案內容如下。


▼看一下測試涵蓋率,好多程式碼都沒被執行到,還要多增加幾組測試資料才行。

***

▼套用前兩集提到的分支涵蓋率突變測試技巧,最後的特徵測試如下。


▼已達到100的分支涵蓋率,也通過手動的突變測試。


▼最終的Golden Master內容:

[foo, -1, 0] => foo, -2, 0
[foo, -1, 1] => foo, -2, 0
[foo, -1, 49] => foo, -2, 47
[foo, -1, 50] => foo, -2, 48
[foo, 0, 0] => foo, -1, 0
[foo, 0, 1] => foo, -1, 0
[foo, 0, 49] => foo, -1, 47
[foo, 0, 50] => foo, -1, 48
[foo, 2, 0] => foo, 1, 0
[foo, 2, 1] => foo, 1, 0
[foo, 2, 49] => foo, 1, 48
[foo, 2, 50] => foo, 1, 49
[foo, 6, 0] => foo, 5, 0
[foo, 6, 1] => foo, 5, 0
[foo, 6, 49] => foo, 5, 48
[foo, 6, 50] => foo, 5, 49
[foo, 11, 0] => foo, 10, 0
[foo, 11, 1] => foo, 10, 0
[foo, 11, 49] => foo, 10, 48
[foo, 11, 50] => foo, 10, 49
[Aged Brie, -1, 0] => Aged Brie, -2, 2
[Aged Brie, -1, 1] => Aged Brie, -2, 3
[Aged Brie, -1, 49] => Aged Brie, -2, 50
[Aged Brie, -1, 50] => Aged Brie, -2, 50
[Aged Brie, 0, 0] => Aged Brie, -1, 2
[Aged Brie, 0, 1] => Aged Brie, -1, 3
[Aged Brie, 0, 49] => Aged Brie, -1, 50
[Aged Brie, 0, 50] => Aged Brie, -1, 50
[Aged Brie, 2, 0] => Aged Brie, 1, 1
[Aged Brie, 2, 1] => Aged Brie, 1, 2
[Aged Brie, 2, 49] => Aged Brie, 1, 50
[Aged Brie, 2, 50] => Aged Brie, 1, 50
[Aged Brie, 6, 0] => Aged Brie, 5, 1
[Aged Brie, 6, 1] => Aged Brie, 5, 2
[Aged Brie, 6, 49] => Aged Brie, 5, 50
[Aged Brie, 6, 50] => Aged Brie, 5, 50
[Aged Brie, 11, 0] => Aged Brie, 10, 1
[Aged Brie, 11, 1] => Aged Brie, 10, 2
[Aged Brie, 11, 49] => Aged Brie, 10, 50
[Aged Brie, 11, 50] => Aged Brie, 10, 50
[Backstage passes to a TAFKAL80ETC concert, -1, 0] => Backstage passes to a TAFKAL80ETC concert, -2, 0
[Backstage passes to a TAFKAL80ETC concert, -1, 1] => Backstage passes to a TAFKAL80ETC concert, -2, 0
[Backstage passes to a TAFKAL80ETC concert, -1, 49] => Backstage passes to a TAFKAL80ETC concert, -2, 0
[Backstage passes to a TAFKAL80ETC concert, -1, 50] => Backstage passes to a TAFKAL80ETC concert, -2, 0
[Backstage passes to a TAFKAL80ETC concert, 0, 0] => Backstage passes to a TAFKAL80ETC concert, -1, 0
[Backstage passes to a TAFKAL80ETC concert, 0, 1] => Backstage passes to a TAFKAL80ETC concert, -1, 0
[Backstage passes to a TAFKAL80ETC concert, 0, 49] => Backstage passes to a TAFKAL80ETC concert, -1, 0
[Backstage passes to a TAFKAL80ETC concert, 0, 50] => Backstage passes to a TAFKAL80ETC concert, -1, 0
[Backstage passes to a TAFKAL80ETC concert, 2, 0] => Backstage passes to a TAFKAL80ETC concert, 1, 3
[Backstage passes to a TAFKAL80ETC concert, 2, 1] => Backstage passes to a TAFKAL80ETC concert, 1, 4
[Backstage passes to a TAFKAL80ETC concert, 2, 49] => Backstage passes to a TAFKAL80ETC concert, 1, 50
[Backstage passes to a TAFKAL80ETC concert, 2, 50] => Backstage passes to a TAFKAL80ETC concert, 1, 50
[Backstage passes to a TAFKAL80ETC concert, 6, 0] => Backstage passes to a TAFKAL80ETC concert, 5, 2
[Backstage passes to a TAFKAL80ETC concert, 6, 1] => Backstage passes to a TAFKAL80ETC concert, 5, 3
[Backstage passes to a TAFKAL80ETC concert, 6, 49] => Backstage passes to a TAFKAL80ETC concert, 5, 50
[Backstage passes to a TAFKAL80ETC concert, 6, 50] => Backstage passes to a TAFKAL80ETC concert, 5, 50
[Backstage passes to a TAFKAL80ETC concert, 11, 0] => Backstage passes to a TAFKAL80ETC concert, 10, 1
[Backstage passes to a TAFKAL80ETC concert, 11, 1] => Backstage passes to a TAFKAL80ETC concert, 10, 2
[Backstage passes to a TAFKAL80ETC concert, 11, 49] => Backstage passes to a TAFKAL80ETC concert, 10, 50
[Backstage passes to a TAFKAL80ETC concert, 11, 50] => Backstage passes to a TAFKAL80ETC concert, 10, 50
[Sulfuras, Hand of Ragnaros, -1, 0] => Sulfuras, Hand of Ragnaros, -1, 0
[Sulfuras, Hand of Ragnaros, -1, 1] => Sulfuras, Hand of Ragnaros, -1, 1
[Sulfuras, Hand of Ragnaros, -1, 49] => Sulfuras, Hand of Ragnaros, -1, 49
[Sulfuras, Hand of Ragnaros, -1, 50] => Sulfuras, Hand of Ragnaros, -1, 50
[Sulfuras, Hand of Ragnaros, 0, 0] => Sulfuras, Hand of Ragnaros, 0, 0
[Sulfuras, Hand of Ragnaros, 0, 1] => Sulfuras, Hand of Ragnaros, 0, 1
[Sulfuras, Hand of Ragnaros, 0, 49] => Sulfuras, Hand of Ragnaros, 0, 49
[Sulfuras, Hand of Ragnaros, 0, 50] => Sulfuras, Hand of Ragnaros, 0, 50
[Sulfuras, Hand of Ragnaros, 2, 0] => Sulfuras, Hand of Ragnaros, 2, 0
[Sulfuras, Hand of Ragnaros, 2, 1] => Sulfuras, Hand of Ragnaros, 2, 1
[Sulfuras, Hand of Ragnaros, 2, 49] => Sulfuras, Hand of Ragnaros, 2, 49
[Sulfuras, Hand of Ragnaros, 2, 50] => Sulfuras, Hand of Ragnaros, 2, 50
[Sulfuras, Hand of Ragnaros, 6, 0] => Sulfuras, Hand of Ragnaros, 6, 0
[Sulfuras, Hand of Ragnaros, 6, 1] => Sulfuras, Hand of Ragnaros, 6, 1
[Sulfuras, Hand of Ragnaros, 6, 49] => Sulfuras, Hand of Ragnaros, 6, 49
[Sulfuras, Hand of Ragnaros, 6, 50] => Sulfuras, Hand of Ragnaros, 6, 50
[Sulfuras, Hand of Ragnaros, 11, 0] => Sulfuras, Hand of Ragnaros, 11, 0
[Sulfuras, Hand of Ragnaros, 11, 1] => Sulfuras, Hand of Ragnaros, 11, 1
[Sulfuras, Hand of Ragnaros, 11, 49] => Sulfuras, Hand of Ragnaros, 11, 49
[Sulfuras, Hand of Ragnaros, 11, 50] => Sulfuras, Hand of Ragnaros, 11, 50

***

廣告

對於軟體測試以及軟體重構有興趣的鄉民,可以參考泰迪軟軟體以下兩個課程:

***

友藏內心獨白:終於可以開始重構了。

2019年2月19日 星期二

透過測試涵蓋率讓重構更有信心(中):突變測試

Feb. 18 23:47 ~ Feb. 19 00:39

▲Ada:切換為超人模式XD


突變測試

如果測試案例涵蓋足夠多的待測程式(System Under Test;SUT)行為,當待測程式被改變時,重新執行所有的測試案例其結果應該失敗。這代表測試案例捕捉到不正確的程式行為(與測試案例所記載的程式行為不符),突變測試(Mutation Testing)的想法就是從此而來—藉由改變待測程式的邏輯,驗證測試案例是否足夠。

***

例子

▼首先手動把withdraw程式的 amount > 0 改成 amount >1,然後重新執行測試案例。如果測試案例涵蓋率足夠多,其執行結果應該是失敗的(因為此時待測程式的行為被更改和原本測試案例所記錄的行為不同)。


▼執行結果居然是綠燈,原本三個測試案例都通過,也就代表原本的測試案例有遺漏了程式的行為,趕快補寫測試案例吧。


▼先把原本withdraw函數的程式碼復原成突變前的正常版本,然後增加一個單元測試來涵蓋剛剛程式碼突變後所沒捕捉到的行為。


▼重新執行全部四個單元測試案例,成功。


▼這時候在手動把withdraw程式的 amount > 0 改成 amount >1。


▼重新執行四個測試案例,執行結果失敗,代表程式的突變行為被測試案例抓到了。換句話說,透過突變測試技巧,進一步增強測試案例的涵蓋度

***


更多的突變

▼再看一次withdraw的程式碼,鄉民們可以試著把 amount > 0 改成 amount == 0、amount < 0,或是把 balance >= amount 改成「小於等於」或是「等於」,來驗證測試案例是否足夠。

以這個例子來看,上述各種突變狀況都可以被四個測試案例給找出來。這時候鄉民們就有相當高的信心,可以開始動手重構,因為「安全網」(足夠的測試案例)已經準備好了。

***


下集預告

withdraw函數的程式邏輯很簡單,就算沒有用突變測試技巧,也可以用傳統的邊界測試(boundary test)技巧來設計測試案例。

下一集將介紹特徵測試(Characterization Test)技巧與Approval Tests工具,針對程式邏輯複雜到爆掉且沒有人看得懂的函數,產生足夠多的測試案例來支援後續重構活動

***

廣告

對於軟體測試以及軟體重構有興趣的鄉民,可以參考泰迪軟軟體以下兩個課程:

***

友藏內心獨白:疫苗接踵次數要足夠才能有完整的抵抗力。

2019年2月18日 星期一

透過測試涵蓋率讓重構更有信心(上):分支涵蓋率

Feb. 18 21:49~22:50

▲很多開發環境都內建測試涵蓋率工具,上圖為Eclipse


如何定義程式的行為?

重構(refactoring)是在不改變軟體外在行為的前提下,改變內部結構以改善設計品質。但是問題來了,你怎麼知道你在重構的過程中沒有改變軟體的行為?這個問題,更廣義一點來說,就是你要如何定義軟體的行為?

學過程式語言的人都知道,程式語言有「語法」和「語意」。語法定義結構,語意定義行為。語法錯誤可藉由編譯器(Compiler)幫忙找出,所以開發人員並不會害怕語法錯誤。

但是,整個程式的行為(語意)是由撰寫程式的人所定義,一般來說編譯器是無從得知開發人員的意圖。在開發人員修改程式的過程中,如果不小心改壞掉,原本可以動的程式不能動了,或是出現未被查知的bug,那就很麻煩了。

正規程式語言定義語意的方法過於抽象,大部分的開發人員無法直接運用,退而求其次,實務上開發人員通常透過撰寫測試案例來描述或紀錄程式的行為

***

測試案例足夠嗎?

透過測試案例採用列舉(舉例子)的方式來描述程式行為,就衍生出另一個問題:你怎麼知道你的測試案例足夠多到可以把程式的(主要)行為都表達出來?在傳統軟體測試領域,這個問題叫做測試案例足夠性條件(test case adequacy criteria),常見的方式是透過測試涵蓋率來判別測試案例是否足夠。

今天不是要討論測試,而是要借用測試涵蓋率的觀念,幫助開發人員在重構前判斷測試案例是否足夠支持後續的重構行為。

***

例子

▼你有一個Account物件用來代表客戶的銀行帳戶,你想要重構withdraw函數,但是目前沒有任何測試案例。雖然withdraw程式碼只有短短幾行,但你還是有點害怕萬一重構了之後程式有問題怎麼辦。


▼因此在重構前你準備補寫測試案例,你很快地設計了以下兩個測試案例:


▼接著你觀察withdraw函數的測試涵蓋率,發現第19行分支(branch)呈現黃色,表示還沒被100%涵蓋。


▼於是你繼續增加一個新的測試案例。


▼此時withdraw函數的程式碼全部變成綠色,代表達到100%的分支涵蓋率。

***

下集預告

達到100%分支涵蓋率並不代表測試案例通過程式就沒有bug,換句話說並不表示程式的所有行為都已被測試案例所記錄下來。但這至少是一個在實務上可行且有一定代表意義的涵蓋率,可以當作預備開始重構前的第一個小目標。

下一集將介紹突變測試(Mutation Testing)技巧,可以進一步驗證測試案例的有效性。

***

廣告

對於軟體測試以及軟體重構有興趣的鄉民,可以參考泰迪軟軟體以下兩個課程:

***

友藏內心獨白:學習測試涵蓋率真的有用。

2019年2月13日 星期三

Scrum團隊如何落實自動化測試與持續整合?

Feb. 13 15:25~16:26


問題

朋友的Scrum團隊跑了半年,每個sprint結束所產生的可執行軟體離「潛在可釋出」還有一大段距離(品質不是很好)。團隊開始思考要如何提升品質,讓每個sprint可以交付「潛在可釋出的產品增量」。

團隊從QA部門借調一位測試人員—QA甲,希望借助他的力量提升軟體品質。但是QA甲以往都是用人工手動測試來驗證功能的正確性,並不會撰寫自動化測試案例。因此,QA甲需要等待團隊成員完成user story之後才可以開始測試。但通常user story完成後也接近sprint尾聲,沒剩多少時間讓QA甲去手動測試。

有團隊成員建議,請QA甲在下個sprint測試上個sprint的功能,但是這又引發出其他的問題,例如:

  • 程式碼凍結:QA甲要測試上個sprint的功能,就必須要求開發團隊不要修改上個sprint所完成的功能。實務上並不可行,因為下個sprint很有可能就是要基於上個sprint所完成的功能繼續開發,要求程式碼凍結會讓開發流程過於僵硬。
  • Done Done:因為上個sprint完成的功能留到下個sprint才驗收,那到底上個sprint做完的功能真的有做完嗎?換句花說,上個sprint的Done不是真的Done,還要經過下個sprint的驗證後才知道是不適真的Done。那如果下個sprint找出上個sprint的bug,又該在何時處理?如此一來,沒完沒了,整個開發時程也不容易掌握。

***

理想狀況

自動化測試與持續整合是軟體開發(不管是否採用敏捷)不可分割的一部分,團隊一開始在撰寫功能之前,就要先把持續整合環境架設好,讓自己的空專案可以在持續整合系統上建構,之後才逐一開發新功能。而每一個新功能,都要有足夠的自動化單元測試、整合測試與驗收測試。

為了達到測試自動化的目的,在Scrum團隊中的QA人員(Scrum團隊成員通稱為開發人員)需要具備撰寫自動化測試案例的能力。如果負責測試的開發人員沒有撰寫自動化測試案例的能力,就要考慮:

  • 是否願意學習新技能,並且與開發人員一起緊密合作
  • 考慮離開Scrum團隊

Teddy的意思並不是說所有的測試案例都必須要自動化,有些測試案例,例如UX方面的測試,或是自動化成本太高的UAT,或是探索式測試,還是可以用人工的方式來做。但大原則是絕大部分的測試必須要自動化,而身處Scrum團隊的測試人員也必須具備撰寫自動化測試案例的能力。

***

非理想狀況怎麼辦

如果團隊已經跑Scrum一陣子,一開始沒有落實自動化單元測試與持續整合,要怎麼「半路出家」?

假設團隊中沒有任何一個人具備自動化測試與持續整合個觀念,最快的方式就是請人教,或是去上課,把基本的知識在短時間先補齊。

接下來可以在DoD(Definition of Done)中訂定自動化驗收測試的標準,讓往後所開發的功能都有基本的自動化驗收測試。至於之前所開發的功能,可以採用以下兩種方式來補寫自動化測試:

  • Bug-driven:當發生bug的時候,先寫一個自動化測試案例來反映這個bug。此時這個測試案例一地會失敗,然後去修改程式碼,直到測試案例通過就知道bug改好了。
  • 預算制度:每個sprint投資固定時間去補寫之前的自動化測試案例。

***

測試先行或測試後行?

有些技術能力比較好的團隊,會採用測試先行(test first),也就是測試驅動開發(test-driven development)、行為驅動開發(behavior-driven development)、實例化規格(specification by example)的方式,讓撰寫測試案例成為開發流程中的上游,test code先於production code,以避免測試後行(test last)的缺點—先寫production code,永遠沒有時間寫test code。

Teddy覺得測試先行或是後行都可以,但重點是「測試一定要行」。只要有紀律,養成測試是開發不可分割的一部分,測試先行或測試後行可以依據團隊的能力與專案特性自行調整。

***

友藏內心獨白:自動化才能提高QA的價值。

2019年2月1日 星期五

繞過去,好嗎?

Jan. 31 23:07~23:58

▲逢山開路,遇水搭橋


問題

從去年開始Teddy和指導教授一起帶幾個實驗室學生做研究,目前遵循clean architecture的方法正在開發看板軟體。一個多禮拜前學生問Teddy關於Repository的介面設計,經過討論後Teddy請他們做點研究,下次開會再談。

今天回學校和學生開sprint review會議,順便請學生說明他們研究Repository的結果。學生告訴Teddy他們發現有兩種Repository的設計方法,第一種是接受由外部給定的specification當作查詢條件,可以比較有彈性的支援多種查詢條件。第二種是在Repository介面上設計常見的方法,例如findAll、findById、findChildrenById等。

學生採用第二種設計方法,聽了請他們選擇的理由後,Teddy還是覺得不滿意(forces沒有被平衡XD)。於是請學生把domain model叫出來,一起讀過一次,發現他們所採用的Repository介面設計無法支援domain model的所有物件。

***

程式可以動啊

學生寫的程式可以動,這個sprint所完成的user story還算做得不錯。但是,如果細看軟體設計,其實還有不少可精進之處。

Teddy告訴他們:

Repository的問題還沒徹底解決,雖然程式可以動,但是如果不管這個設計問題,繞過去,以後你們還是有很大的機會會再遇到它。你們是研究生,專案中遇到問題應該利用機會把問題搞懂,徹底解決。日後遇到同樣的問題,既使context不同,別人花三天,你們只要花一小時就可以解決。層次與專業就是這樣累積出來的。

這個問題,最好的解決方式就是你們搞懂後做出設計的決定,然後說服我(教我)。次一等的解決方式,就是我弄懂後教你們。最糟的方式就是不管它,反正程式可以動就好。

***

Repository Pattern

因為Google太方便,所以學生遇到問題尋找資料,大多下個查詢條件,然後看最前面的幾個連結之後就做出結論,草草結束。Teddy在〈增進學習力的三個練習〉提到,首先要知道名詞的定義。學生只知道可以套用Repository pattern,但很可能對於該pattern的定義只是一知半解。

在《Patterns of Enterprise Application Architecture》的第322頁就介紹Repository pattern。在《Domain-Driven Design: Tacking Complexity in the Heart of Software》以及《Implementing Domain-Driven Design》也都有提到,後者並且包含範例程式碼與實作細節。

***

龜毛

大家都說日本人做事很龜毛。同樣的商品,例如Toyota汽車,MIT和MIJ,相信大部分的人還是比較喜歡日本原裝進口。為什麼?因為組裝的人不一樣啊…Orz

軟體開發是一種專業,就好像醫生也是一種專業。你總不希望醫生開刀的時候遇到問題「繞過去」吧?!反正 程式可以動 人還有呼吸就好XD。 

***

友藏內心獨白:書還是要讀,不能只看網路文章。

2019年1月31日 星期四

只聘請願意寫單元測試的軟體工程師

Jan. 30 17:48~19:00

▲古早味的VB程式


一個測試,各自表述

一位從事測試工作的朋友告訴Teddy,他在應徵工作時主管告訴他,公司的工程師都有做單元測試。到職之後,有一天朋友去找工程師…

朋友:我可以看一下你寫的單元測試程式嗎?

工程師:什麼程式?

朋友:單元測試程式,主管說你們都有做單元測試。

工程師:喔,單元測試喔。有啊,不過我們都是手動測試,自己手動測自己寫的function。

朋友:(黑人問號???)

***

自動化

單元測試是指對程式可執行的最小單位(單元)進行測試,可能是function或是object。一般而言單元測試如果沒有特別強調,預設狀況下應該會認為是自動化單元測試,也就是透過單元測試程式碼(unit testing code)去測試生產程式碼(production code)。

Teddy剛開始寫第一個自動化單元測試是N年前還在用VB 6.0開發Windows軟體的時候。寫過VB(非現在的VB.NET)程式的鄉民都知道,VB的程式邏輯很容易和UI元件產生緊密耦合,必須透過使用者介面來測試系統功能。Teddy從台北工專電子科畢業,然後當兵,到退伍,寫了好幾年的VB程式,獨自開發7~8個不同的系統。

一開始也都是自己手動測試系統功能,但是時間久了之後覺得好累,因為手動的「廻歸測試」簡直不是人幹的工作,太花時間、累人又無聊。但是如果不手動「廻歸測試」,什麼時候修改功能造成bug又不知道。等到客戶踩到雷回報錯誤,又有漫長的除錯工作和加不完的班,也是很討厭。

後來JUnit被發明且流行之後,Teddy才知道原來有自動化單元測試這種東西。可惜當時沒有免費的VB Unit工具,從網路上花了幾十塊美元買了一個VB Unit工具,正要興高采烈的寫單元測試時,才發現寸步難行。因為VB程式的邏輯很容易全部都寫在使用者介面中,根本無法單獨測試邏輯

***

相依性分離

後來才知道原來VB可以寫OCX與DLL元件,沒有使用者介面的程式邏輯可以寫在Class裡面包成DLL(VB 6也有Class,支援介面繼承但不支援實作繼承)。把程式邏輯從UI元件抽離,改放到DLL之後,便可以對DLL寫自動化單元測試。

有了這次經驗,開始愛上單元測試。雖然自動化單元測試不能完全取代透過使用者介面執行的驗收測試,但可以大幅減少手動測試的數量,而且當單元測試案例執行失敗時,也可以減少除錯的時間。

另外還有一個好處,就是為了提高自動化測試的程度,程式的模組化也提升。以前Teddy很瞧不起VB 6,認為它不是一個「完整的物件導向語言」(因為不支援實作繼承)。但後來又讀了〈Design Patters〉,才知道原來應該多用物件聚合少用實作繼承。VB 6也可以寫出很棒的物件導向程式以及套用設計模式。

***

今天暫時停止

近20年後的今天,在台灣會寫單元測試的工程師已經很多了……才怪。和20年前相比的確是變多了,但是以全體程式開發人員當分母來看,工作上把「撰寫單元測試當作軟體開發不可分割的一部分」的工程師還是少數。這種狀況不知道是要歸咎於台灣的資訊教育有待加強,還是職場的大環境不支持。

一個沒有自動化單元測試的團隊,手邊的軟體不是「軟體」,而是「硬體」,硬梆梆的沒人想改也不敢改。這種情況還學人家談什麼敏捷開發、擁抱改變,早點洗洗睡吧。

***

軟體工程師要提升自己的價值,除了學好物件導向技術以外,最立竿見影的方法,就是開始寫自動化單元測試。

***

友藏內心獨白:一試成主顧。

2019年1月30日 星期三

流程權威

Jan. 29 21:28~22:31

▲起床了


問題

上周末在泰迪軟體上【Scrum敏捷方法實作班】,休息時間有一位學員問Teddy…

學員:《Essential Scrum》書中提到Scrum Master的責任,其中有一點是「流程權威(Process Authority)」。請問我要怎麼讓自己變成流程權威?

***

狹義來說

Scrum Master協助團隊、Product Owner、與公司獲得Scrum的好處(請參考〈Scrum Master 的存在〉) 。因此Scrum Master對於Scrum框架與價值、原則、實務做法等,都必須要非常熟悉。

換句話說,Scrum Master必須是敏捷專家。這一點聽起來好像很簡單,但Teddy遇過很多Scrum Master,即使已經上過CSM、CSPO的課程,但對於基本的敏捷精神與Scrum框架還是一知半解。

舉幾個例子:

  • 只有Product Owner能撰寫user story。
  • 只有在review meeting時可以review做完的user story。
  • Sprint長度越短越敏捷,所以團隊不管怎樣就是要採用一周一個sprint。
  • 在Review meeting討論流程改善,在retrospective meeting討論需求。
  • 把cross-functional與multi-skills搞混。
  • 把actively doing nothing搞成passively doing nothing。
  • 這個sprint兩個禮拜功能做不完,所以動態調整把sprint改成三周。
  • Sprint review只管完成user story的個數,忽略交付給使用者的價值。

***

廣義來說

Teddy認為敏捷開發的各個派別,XP、Scrum也好,精實開發、看板方法也罷,只是不同人對於追求 「The timeless way of software development(軟體開發的永恆之道)」所提出來的見解。

「能量不滅」,傳統軟體工程所探討的問題,敏捷開發幾乎都會碰到,只是敏捷開發採取的應對策略可能有所不同,但要解決的共同大問題就是那一個。所以要成為流程權威,就必須知道軟體工程所探討的問題,然後回頭對應敏捷方法如何處理這些問題。

最好有自己參與過軟體開發的經驗,包含專案與產品開發,與不同程度與背景的人合作,越多樣性越好。親身體驗軟體開發的各種forces(作用力、限制條件),越能夠感受軟體開發的Quality Without A Name(無名的特質)。

***

誰適合當Scrum Master

經常有朋友問Teddy:「哪種人適合當Scrum Master?」依據《Essential Scrum》的看法,Scrum Master必須是:

  • Coach
  • Servant leader
  • Process authoring
  • Interference shield
  • Impediment Remover
  • Change Agent

以上任何一點只要做到極致,就已經是該領域的專家了。而Scrum Master居然要同時精通「武當六絕」,這根本需要百年難得一見的練武奇才方能達成使命。難怪有一位HR曾經跟Teddy開玩笑說:「找Scrum Master好像在找聖人一樣」。

誰適合當Scrum Master?不同背景的人,只要具備上述能力其一,就有機會擔任Scrum Master。Scrum Master也是人,也需要持續學習。如果是coach背景的人當任Scrum Master,也許對於軟體開發流程不熟,就需要補足這方面的能力。如果是老闆的兒子或女兒當任Scrum Master,應該在阻礙排除、干擾屏蔽與變革代理有很大的貢獻,但其他責任可能就需要補強。

總之,將相本無種,人類當自強。

***

友藏內心獨白:把自已當成一座橋梁,理論與實務的橋樑。