上大學的時候學過軟件測試這門課,但是在公司實習時才真正實戰了一把。先花了半個月把Junit In Action 英文版看完了(話說在公司學習效率就是比學校里高啊) 廢話不多說,直接開始主題。
為什么要寫單元測試?
兩個理由:1.給我們重構的信心(give us the confidence to refactor)。一堆糾纏而無測試的代碼你敢隨便修改?
2.好的單元測試就是文檔(documenting expected behavior)。幾個實用的例子比文檔讓人感興趣的多。
概念就不說了,黑盒白盒TDD,語句路徑覆蓋率,網上都有,直接講講我的收獲吧。
我在項目里最初就是給一些基礎類(EntityModel)寫測試,它是一個抽象類,內部保存Entity,並執行增刪改查的操作。那么我第一個遇到的問題就是:如何測試一個抽象類?方法很簡單,就是去定義一個子類,然后生成子類的實例做測試。拿測試update函數來說,有三個步驟:
1.准備一個Entity,給他設置兩個屬性,age = 10,weight = 50。
2.把它add進EntityModel,進行update操作,修改他的weight = 80。
3.最后get出來,驗證得到的Entity weight = 80 且 age = 10。
代碼如下:
@Test public void testUpdateEntityWhenNotTheSameProperty() { MyEntity myEntity = createMyEntity(AGE_ID, WEIGHT_ID, 0L); entityModel.addEntity(myEntity); entityModel.updateEntity(myEntity.getClass(), myEntity.getId(), MyEntity.PROPERTY_NAME_AGE, DIFF_AGE_ID); assertEquals(DIFF_AGE_ID, myEntity.getAge()); }
而這三個步驟總結起來就是"准備-構建-驗證"(《clean code》 , Unit Test chapter),測試用例都遵從這個步驟。
寫着寫着,你會遇到一些問題。怎么驗證我的函數里面調用了一個靜態方法去記錄log日志?怎么驗證一個私有方法?怎么測試一個接口?怎么測試向一個數據庫插入數據的操作?怎么驗證我測試的程序一定會拋出一個異常?……這下就要到網上去查了,有許多現成的框架可供使用,大名鼎鼎的Junit;Mockito可以模擬接口,模擬函數返回值,驗證方法調用次數;Powermock(查看需翻牆)可以模擬靜態方法和final方法;Java的反射機制可以模擬私有方法。
給函數起名也有講究。我一般用test__when__thenReturn__的結構,盡量讓看的人不用看代碼就能知道功能。
不能為了省事而把不同性質的驗證放在一個test中,一次只做一件事,做好一件事。
而且最重要的是,在寫單元測試過程中,我慢慢就對代碼的邏輯清楚了。如果必須要覆蓋每個判斷,等我寫完測試用例,我就已經知道這個函數的每個細節。這對於以后重構很有幫助。
寫的不多,但是我要堅持寫下去,因為總結和思考的過程就是成長。
——周五晚在公司
--------------------------------------------
今天又學到一招,mock過靜態方法的朋友知道,需要在類前添加
@RunWith(PowerMockRunner.class)
@PrepareForTest(xxx.class)
而如果我需要在一個類中驗證兩個不同類的靜態方法,只需要
@PrepareForTest({Hello.class, World.class})
或是
@PrepareForTest(fullyQualifiedNames = {"com.util.log4j.LogManager","com.rcm.model.risk.defs.BettingFamilies"})
——2015/2/2