l
顯示具有 Eclipse 標籤的文章。 顯示所有文章
顯示具有 Eclipse 標籤的文章。 顯示所有文章

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)技巧,可以進一步驗證測試案例的有效性。

***

廣告

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

***

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

2013年8月7日 星期三

BDD(5):第一個Cucumber-JVM範例,下集

August 06 09:49~11:18

image

 

離上次寫BDD文章一轉眼又過了快兩個禮拜,不知道鄉民們是否還記得《BDD(4):第一個Cucumber-JVM範例,上集》的內容?先幫大家複習一下,在上集中Teddy提到BDD—「行為驅動開發」的六個步驟,並介紹了前面四個步驟:

  1. 先定義驗收測試條件,也就是應用程式應有的行為。
  2. 然後執行驗收測試,這時候因為找不到step definition而測試失敗。
  3. 定義step definition。
  4. 然後執行驗收測試,這時候因為step definition的內容是空的所以測試失敗。
  5. 填寫step definition的內容,在這個步驟鄉民們會開始思考production code的設計與實作。
  6. 當production code完成,整個驗收測試案例便可通過(或是反過來說,當最後驗收案例通過,就代表production code已經完成)。

今天要繼續介紹5、6兩個步驟。

***

在上集中,最後請鄉民們新增一個名為HelloStepdefs的Java class,然後把Console view裡面Cucumber-JVM所顯示的三個step definition拷貝起來複製到HelloStepdefs類別中,如下圖所示。

螢幕快照 2013-07-23 下午10.18.48

現在萬事俱備,只欠 東風 production code。接下來要完成上述的步驟5,藉由填寫step definition的內容來協助鄉民們思考production code的設計與實作 。

 

實作第一個Step Definition

第一個step definition:「I have a greeting application with "Hello"」告訴鄉民們,需要「生出」一隻「greeting application」,並且把"Hello"這個字串傳給他。有了這個行為的定義,就可以填寫第一個step definition的內容,如下圖所示。

螢幕快照 2013-08-06 上午10.26.27

 

在第9行宣告一個Hello類別的instance variable “hello”來代表「greeting application」(類別的名稱可以自己取,不一定要叫做Hello),然後在「 I_have_a_greeting_application_with(String arg1)」這個method裡面,初始化hello,並且把參數arg1傳給hello。arg1在Cucumber-JVM執行測試案例的時候,會帶入使用者在hello_world.feature檔案中第一個step definition內容:「 Given I have a greeting application with "Hello"」用引號所引號起來的參數,也就是"Hello"這個字串。

填寫完第一個step definition之後,鄉民們發現程式根本無法通過編譯,因為Hello類別根本還沒有產生。此為正常現象,所以才叫做BDD啊挑眉質疑。要怎麼讓程式可以編譯成功?很簡單,請鄉民們新增一個Hello class,並且幫它產生一個可以接受一個字串參數的constructor(建構元)。然後在Hello class裡面宣告一個字串型態的instance variable "greeting",用來保存constructor傳入的字串參數。

螢幕快照 2013-08-06 上午10.27.51

 

完成Hello class撰寫之後,HelloStepdefs現在已經可以成功編譯。

螢幕快照 2013-08-06 上午10.31.30

 

這時候執行一下JUnit,原本三個step definition都是灰色的,現在第一個step definition已經有一個小勾勾,代表執行測試通過。

螢幕快照 2013-08-06 上午10.33.28

 

切到Console view,發現第二個step definition還沒有定義。

螢幕快照 2013-08-06 上午10.36.45

***

實作第二個Step Definition

第二個step definition:「I ask it to say hi」翻譯成「非白話文」的意思,就是告訴鄉民們呼叫hello物件的sayHi method(method名稱可以自己取,不一定要叫sayHi)。

螢幕快照 2013-08-06 上午10.44.22

 

看到這裡鄉民們應該看出個端倪出來,第18行的hello.sayHi()一定也無法通過編譯,因為Hello類別根本沒有sayHi這個method。要讓程式通過編譯,請鄉民們幫Hello類別新增sayHi()這個method。

螢幕快照 2013-08-06 上午10.47.51

 

完成之後HelloStepdefs就可以成功編譯。

螢幕快照 2013-08-06 上午10.49.14

 

這時候執行一下JUnit,只有第一個step definition有一個小勾勾,現在第二個step definition也通過測試。

螢幕快照 2013-08-06 上午10.50.36

***

 

實作第三個Step Definition

終於來到最後一個步驟,第三個step definition:「I receive "Hello World"」翻譯成「非白話文」的意思,就是說呼叫完hello物件的sayHi method之後,會得到"Hello World"這個字。鄉民們可以在第三個step definition「I_receive(String arg1)」這個method裡面,用JUnit的assertEquals(String, String)來比對sayHi是否傳回"Hello World"這個字。在這裡"Hello World"所謂的expected result,就是測試結果的期待值,事先計算好的正確答案。這個expected result已經定義在hello_world.feature檔案中,Cucumber-JVM在執行測試案例的時候會自動傳入給I_receive(String arg1)」這個method。

螢幕快照 2013-08-06 上午10.56.15

 

現在問題來了,剛剛在步驟二實作sayHello()的時候,並沒有傳出任何的資料出來,所以現在沒有東西可以比對。步驟三告訴鄉民們,sayHello()要傳回"Hello World",為了讓測試程式可以通過編譯,要回頭改一下剛剛寫好的程式。

下圖第11行程式中,在HelloStepdefs類別中宣告一個String型態的instance variable “hi”,然後在第20行用hi來接收sayHi()傳回來的資料。最後,在第25行中,拿hi和arg1相比。

螢幕快照 2013-08-06 上午11.05.59

 

因為sayHi()並沒有傳回任何資料,所以此時測試程式無法編譯,請回頭修改sayHi()的實作。

螢幕快照 2013-08-06 上午11.11.25

 

修改完sayHi()之後,測試案例可以通過編譯。

螢幕快照 2013-08-06 上午11.12.48

 

這時候執行一下JUnit,現在三個step definition都有一個小勾勾,代表驗收測試案例的三個步驟全部通過,也就是說production code已經完成客戶所需要的功能了。

螢幕快照 2013-08-06 上午11.13.57

***

友藏內心獨白:寫完收工。

2013年7月26日 星期五

BDD(4):第一個Cucumber-JVM範例,上集

July 23 21:12~22:45

看了前三集,假設鄉民們已經知道BDD的基本觀念、Cucumber運作原理、以及如何在Eclipse中執行Cucumber-JVM,在這一集利用一個簡單但完整的範例,介紹一個使用Cucumber-JVM開發應用程式的例子。

***

新增Feature檔案

這個例子在《BDD(2):大家來吃小黃瓜之Cucumber運作原理》已經介紹過了,客戶要你開發一支應用程式,這支程式有一個功能叫做Greeting。在撰寫程式之前,客戶(或是開發團隊)先幫這個功能寫出驗收測試文件。

螢幕快照 2013-07-23 下午9.25.16

 

Cucumber-JVM的驗收測試檔案要描述在.feature的文字檔當中。請鄉民們先用Eclipse建立一個Java專案,然後新增一個名為test的source folder。在這個目錄中建一個resources目錄,然後新增一個文字檔hello_world.feature,檔案內容就是上圖中的文字。

螢幕快照 2013-07-23 下午9.28.15

***

執行測試案例

Cucumber-JVM整合了JUnit 4,要執行上面這個驗收測試,請鄉民們先新增一個名為GreetingTest的Java class(檔案可以隨便取,只要開發團隊知道這個class是用來代表Greeting這個feature就可以了)。

螢幕快照 2013-07-23 下午9.39.28

 

新增CreetingTest class之後,在class上面貼上@RunWith@Cucumber.Options這兩個annotation。

螢幕快照 2013-07-23 下午9.44.45

@RunWith這個annotation告訴JUnit 4,這一個測試案例請用Cucumber.class來執行它,而不要用JUnit 4預設的runner(因為JUnit 4看不懂Cucumber-JVM的驗收測試格式)。@Cucumber.Options裡面包含傳給Cucumber.class執行驗收測試時的參數,features這個參數告訴Cucumber.class,請執行Java classpath裡面的resources/hello_world.feature這個檔案中的驗收測試(也就是檔案中的每一個scenario)。

寫好之後用JUnit執行這個測試。

螢幕快照 2013-07-23 下午9.53.23

 

執行結果如下圖所示。看到JUnit變「綠燈」不要高興得太早,其實沒有任何測試案例被執行。

螢幕快照 2013-07-23 下午9.51.51

 

真正的「綠燈」畫面應該是這個樣子,每一個測試案例前面會有一個「打勾」的小icon。這是鄉民們接下來要努力的目標。

image

****

撰寫膠水程式

執行完JUnit之後,請鄉民們切換到Eclipse的Console view,會看到如下的畫面。

螢幕快照 2013-07-23 下午9.59.26

 

Cucumber-JVM好心提醒鄉民們,它找不到這個feature裡面的scenario的每一個step相對應的step definition程式(相關名詞說明請參考《BDD(2):大家來吃小黃瓜之Cucumber運作原理》,step definition就是所謂的膠水程式)。為什麼找不到?因為根本還沒寫…挑眉質疑。所以下一步就是要撰寫step definition,Cucumber-JVM已經告訴鄉民們,有三個step definition要撰寫。請鄉民們新增一個名為HelloStepdefs的Java class(檔案名稱可以隨便取),然後把Console view裡面的這三個step definition拷貝起來複製到HelloStepdefs。

螢幕快照 2013-07-23 下午10.18.48

寫好之後執行JUnit,發現這次錯誤訊息不同。Cucumber-JVM找到step definition,但是step definition的內容尚未實做。所以接下來的步驟就是要去填滿step definition的內容,讓測試案例可以通過。

螢幕快照 2013-07-23 下午10.21.22

***

今天先練習到這邊,下一集再繼續完成後半段的練習。看到這邊鄉民們不知道有沒有感受到一點BDD—「行為驅動開發」的味道:

  1. 先定義驗收測試條件,也就是應用程式應有的行為。
  2. 然後執行驗收測試,這時候因為找不到step definition而測試失敗。
  3. 定義step definition。
  4. 然後執行驗收測試,這時候因為step definition的內容是空的所以測試失敗。
  5. 填寫step definition的內容,在這個步驟鄉民們會開始思考production code的設計與實作。
  6. 當production code完成,整個驗收測試案例便可通過(或是反過來說,當最後驗收案例通過,就代表production code已經完成)。

下集會介紹5、6兩個步驟,敬請期待。

***

友藏內心獨白:到目前為止都還蠻簡單的。

2013年7月22日 星期一

BDD(3):在Eclipse執行Cucumber-JVM

July 19 15:23~17:35

螢幕快照 2013-07-19 下午5.33.45

 

Teddy在這一集介紹如何在Eclipse裡面使用Cucumber-JVM,為了避免混淆,Teddy再強調一下:Cucumber是用Ruby開發的BDD工具,Cucumber-JVM是用Java開發的「Cucumber山寨版」。使用Cucumber-JVM不需要安裝Ruby,只需要下載幾個jar檔並設定到Java的classpath即可。

在Eclipse裡面使用Cucumber-JVM有好幾種方法,理想上如果有人幫Cucumber-JVM開發Eclipse plug-in的話,那安裝plug-in會是最簡單的方法。但是Teddy稍微找了一下,目前似乎沒有很好的Cucumber-JVM plug-in,所以暫時先排除這種方式。如果熟Maven或是Ivy的鄉民們,可以用Maven或是Ivy來管理Cucumber-JVM所需要的jar檔。下圖是Maven的pom.xml檔案範例,把這個檔案放在Eclipse裡面,

螢幕快照 2013-07-19 下午4.45.03

 

並把Maven Dependencies加到Eclipse的Java Build Path裡面就可以了。

螢幕快照 2013-07-19 下午4.47.30

***

在這裡Teddy要介紹最簡單的「老師傅手工打造」方法,自己下載所需要的jar。要執行最基本的Cucumber-JVM功能,需要五個jar檔,目前Teddy使用的檔案版本如下 :

  • cucumber-java-1.1.3.jar
  • cucumber-core-1.1.3.jar
  • cucumber-junit-1.1.3.jar
  • junit-4.1.1.jar
  • hamcrest-core-1.3.jar

用這些檔名當「祭品」拜一下Google大神,就會找到可以下載的連結。下載完畢之後在Eclipse新增一個Java Project,然後建一個libs目錄,把這五個jar檔複製到libs目錄中。

螢幕快照 2013-07-19 下午4.54.33

 

完成之後把這個五個jar檔加入Eclipse的Java Build Path。

螢幕快照 2013-07-19 下午4.01.30

 

基本上這樣就完成了,看一下Teddy在《BDD(2):大家來吃小黃瓜之Cucumber運作原理》所舉的範例專案目錄結構。

螢幕快照 2013-07-19 下午5.00.35

***

雖然環境設定好之後就可以開始體驗BDD,但是目前Teddy還沒找到支援Cucumber-JVM的好用plug-in。用最陽春的方式開發,有一些不太方便的地方。例如,如下圖所示,執行驗收測試案例之後(.feature檔案裡面的scenario),在JUnit視窗中,無法直接double-click某一個step就跳到相對應的step definition程式碼裡面。

螢幕快照 2013-07-19 下午5.12.45

 

Double-click JUnit視窗中的項目會出現以下錯誤訊息。

螢幕快照 2013-07-19 下午5.12.08

 

另外,編輯.feature檔案的時候,沒辦法直接跳到相對應的step definition程式碼。

螢幕快照 2013-07-19 下午5.14.54

 

同樣地,在step definition程式碼中也無法跳到.feature檔案中相對應的step。

螢幕快照 2013-07-19 下午5.16.34

***

今天的範例程式可以在「搞笑談軟工Facebook社團」中下載,檔名是Teddysoft-CucumberTest-V1.jar

***

友藏內心獨白:若有好用的Cucumber-JVM plug-in請介紹一下。

2013年4月1日 星期一

EclEmma:免費Eclipse Java測試涵蓋率工具

Mar. 31 00:12~13:19

螢幕快照 2013-03-31 上午1.10.37

 

Teddy在3月22~23日的「單元測試與持續整合實作班」(請參考《單元測試與持續整合實作班課程實錄》)介紹了測試涵蓋率的用途,並且使用EclEmma這個Eclipse外掛程式協助開發人員觀察Java程式的statement coverage(line coverage)與branch coverage。今天介紹一下這個免費又方便好用的工具。

EclEmma的官方網頁在此,目前最新的版本是2.2.0。EclEmma是一個Eclipse外掛程式,底層原本是呼叫Emma這個工具來計算Java程式的測試涵蓋率。但自從2.0版之後,EclEmma已經改用JaCoCo這個工具,而不再使用Emma。EclEmma雖然已經和Emma這個工具沒有關係了,不過EclEmma的名字卻沒有因此改為EclJaCoCo,如果沒留意會造成一點小困擾(Emma和JaCoCo所支援的測試涵蓋率種類有點不同)。

***

安裝EclEmma

連到EclEmma的官方網站上面會有如何在Eclipse上安裝EclEmma的介紹,方法很簡單,在Eclipse的選單中選Help->Install New Software…,輸入EclEmma的update site位址http://update.eclemma.org/,細節就不多做說明了。

螢幕快照 2013-03-31 上午12.25.44

 

安裝完成之後可能需要重新啟動Eclipse,之後會在Eclipse上方的工具列上出現一個新的按鈕。

螢幕快照 2013-03-31 上午12.31.22

 

待測程式(Production Code)

在寫測試程式之前先看一下待測程式長成什麼鳥樣子。等一下要用單元程式分別達到100%的statement coverage與branch coverage,所以待測程式也非常簡單,裡面只有一個if敘述外加兩個System.out.println(),分別印出兩個不同的執行路徑。

螢幕快照 2013-03-31 上午12.37.08

 

100% Statement Coverage

要達到100%的statement coverage只要讓測試程式可以執行到待測程式ifMethod()的每一行程式就可以了。如何做到?很簡單,傳入true這個參數就OK了。以下是測試程式。

螢幕快照 2013-03-31 上午12.47.29

 

在測試案例上按下滑鼠右鍵,選Coverage As->JUnit Test。

螢幕快照 2013-03-31 上午12.48.21

 

EclEmma提供了一個Coverage view,可以看到statement coverage已達到100%。

螢幕快照 2013-03-31 上午12.51.21

 

切換到production code,綠色的表示該行程式100%被執行,紅色表示完全被執行,黃色表示有部分沒有被執行。剛剛的測試案例,由於傳入參數的數值是true,if (aBool)只涵蓋了true這個路徑,false沒有被涵蓋到,因此這一行程式碼被用黃色標示。

螢幕快照 2013-03-31 上午12.52.13

 

if(aBool)這一行右邊有一個小菱形符號,將滑鼠移到上面顯示1 of 2 branches missed

螢幕快照 2013-03-31 上午12.55.37

 

100% Branch Coverage

要達到100%的branch coverage也很簡單,修改一下測試程式。

螢幕快照 2013-03-31 上午12.59.05

 

執行Coverage As,再觀察一下Coverage view。這裡有一個小地方要注意一下,由於Coverage view一次只能顯示一種coverage,要觀察branch coverage,必須將Coverage view切換成顯示Branch Counters。

螢幕快照 2013-03-31 上午1.04.25

 

切換完畢之後顯示待測程式有2個branch,而且branch coverage是100%。

螢幕快照 2013-03-31 上午1.06.55

 

切回production code,原本的黃色這一行也變成綠色了。

螢幕快照 2013-03-31 上午1.08.09

***

觀察測試涵蓋率的原因是希望知道自己專寫的測試案例是否足夠,而且有真正測試到待測程式的各種不同執行路徑。但是看到這邊鄉民們可能會想:我連寫測試程式的時間都不夠了,哪有可能還去關注測試涵蓋率啊。對於加班加到家人都快不認識你的工程師而言,話是這樣說沒錯。但萬一有哪一天真到需要寫單元測試,對於那些邏輯比較複雜、比較容易出錯、或是重要性比較高的程式,有工具可以幫助了解測試程式的有效性,以及提醒自己還有哪些情況沒有測試到,也是挺不錯的。

***

友藏內心獨白:耶,測試程式裡面為什麼沒有任何assertion呢?