綠燈 | 紅燈

妙思資訊

2012年4月14日 星期六

來空空,去空空

保持乾淨是Unit Test 的原則之一。
怎麼說呢? 進行測試前,先要有乾淨的環境測試結果才會正確。當測試完後,也要把環璄整理乾淨,留給下個測試者乾淨的空間。

保持乾淨,常常用在對資料庫的資料作測試。進行測試前,要先確保測試Table是空的,然後才開始測試。測試的過程中可能會 Insert 資料或 Update 資料,當測試結束後,就把測試Table中的資料清空。這樣,下一個 Unit Test 才不會被先前測試時所遺留的資料干擾。

如何在每次測試前都有乾淨的Table 呢?

第一種作法就是在每次測試完後,使用SQL 刪除資料,不過這種作法會很累人。

第二種作法就是在每支測試方法上再包一個Transaction,當測試完成後Transaction 自動Rollback,這樣就不會有任何資料被存入,測試Table永遠保持乾淨。

使用Transaction 自動 Rollback 是最佳的方法,可以使用Spring 的 SpringJUnit4ClassRunner 進行設定。先寫一個SpringTXBaseTest,如果有需要Transaction Rollback 的Test 就只要繼承 SpringTXBaseTest 即可。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/applicationContext*.xml"})
@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true)
@Transactional
public abstract class SpringTXBaseTest {}

public class DaoTest extends SpringTXBaseTest {

@Autowired
private MemberDao dao;
 
@Test
public void testSelCountByAcc(){
  
String acc="7R6";
ClubMember member=OM.createPersistClubMember(acc);
dao.saveOrUpdate(member);
assertTrue(dao.selCountByAcc(acc)>0);
  
}
}

在DaoTest中的每支Test都有包Transaction,所以testSelCountByAcc()執行完後,就會自動Rollback。如果好奇想要看看資料是否真的可寫入資料庫,只要加上@Rollback(false)即可。確定資料可寫入資料庫後,記得要把資料刪除保持乾淨。

@Rollback(false)
@Test
public void testSelCountByAcc(){
  
String acc="7R6";
ClubMember member=OM.createPersistClubMember(acc);
dao.saveOrUpdate(member);
assertTrue(dao.selCountByAcc(acc)>0);
  
}

2012年4月3日 星期二

測試 For 迴圈不要忘了測Size 大小

一般測試 Collection 的資料如 List, Set 都會使用迴圈來進行測試。如果你的測試碼如下,可能就會有問題
//測試 getBigHouses()所取出的房子都是>50坪的房子
@Test
public void testHousePing(){
  
 List<House> houses=service.getBigHouses();
  
 for (House house : houses) {
   assertTrue(house.getPing().intValue()>50);
 }  
}

乍看之下好像沒有錯誤,但是如果getBigHouses() 本身就寫錯了,根本就沒有取出任何House,這時跑這支Test 仍舊會過,因為 service.getBigHouses() 取出的 houses 是空的 ,所以根本沒有進入迴圈,因此Test 再沒有任何的assert 下就過了。

所以在進行迴圈測試時,一定要先測Size,確定有資料取出,才不會造成空迴圈測試仍過的誤判。


//測試 getBigHouses()所取出的房子都是>50坪的房子
@Test
public void testHousePing(){
  
 List<House> houses=service.getBigHouses();
 assertTrue(houses.size()>0); //一定要先測size
 for (House house : houses) {
   assertTrue(house.getPing().intValue()>50);
 }  
}

你開發的程式好測試嗎(七)?

第六種情況

測試  private method.

因 private method 不好測,若要進行單元測試可採用以下幾種方式:
1. 透過 public method 間接呼叫 private method 來測試.
2. 如果private method 可以改成default method ,只要source class 和test class有相同的package 名稱,即可對default method 進行測試。
3. 使用reflection 直接對private method 進行測試。

透過 public method 間接呼叫 private method 來測試


使用public method 來間接測試 private method 是最簡易的方式。但是如果public method 沒有管道取得或回傳private method 的結果,那也是無法測;另外,比較不好的原因是2個method couple 在一起,萬一將來private method 改了,為了要能測試 private method ,public method 有可能也會一起改。

例:  public method getTenant(String name) 間接呼叫 private method findByName(String name)

public Tenant getTenant(String name){
  
   return findByName(name);
}  
 
private Tenant findByName(String name){
  
    return dao.getTenantByName(name);
  
}

直接呼叫public method getTenant() 來測試
@Test
public void testPrivateMethodByPublicMethod(){
  
 String name = "Tom";
 Tenant tenant=service.getTenant(name);
 assertEquals(name, tenant.getName());
}

使用 default method 來測試


將 private method 改為default method,並將source class和 test class 的package 設成一致,便可直接對此default method 進行測試。

例:   下圖中要測試的是RentServiceImpl 的 default method  findByName(String name) 。且Test Class (DefaultMethodTest) 和 Source Class (RentServiceImpl) 的package name 皆為 rent.report

    

//default method
Tenant findByName(String name) {

 return dao.getTenantByName(name);

}

對default method 進行單元測試
package rent.report;

import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import rent.demo.Tenant;

public class DefaultMethodTest {

 private RentServiceImpl service;

 @Before
 public void setUp() {

  service = new RentServiceImpl();  
 } 
 
 @Test
 public void testPrivate2DefaultMethod() {
  String name = "Tom";
  Tenant tenant = service.findByName(name);
  assertEquals(name, tenant.getName());
 }

}

使用reflection 直接對private method 進行測試

最後,如果對上述二種測試方式都不滿意,那就只能利用reflection 的方式直接對private method進行測試。如果不想自己寫reflection 的程式碼,最方便的方法就是去下載 junit-addons  可以經由  PrivateAccessor 的 invoke() 來呼叫 private method.

invoke(Object object, String methodName, Class[] parameterTypes, Object[] args) 
第一個參數 object: 有此待測方法的物件. 
第二個參數 methodName:待測方法
第三個參數 parameterTypes:傳入待測方法的參數型態
第四個參數 args :傳入待測方法的參數值

以 RentServiceImpl 的 findByNme(String name) 為例,第一個參數就是 RentServiceImple 的 instance service;第二個參數就是 "findByName"; 第三個參數就是  new Class[] { String.class }; 第四個參數就是new Object[]{"name"}.

例: 測試  private Tenant findByName(String name)

@Test
public void testPrivateMethod() throws Throwable {

 String name = "Tom";
 Tenant tenant = (Tenant) PrivateAccessor.invoke(service, "findByName",
    new Class[] { String.class }, new Object[] { name});
  
 assertTrue(tenant.getName().equals(name));
}
最後要注意的是,當進行refactory 將此private method name變更時,因invoke 的第二個參數是使用method 的字串名稱,所以不會隨refactory 同步更換,要自己手動將此method 名稱變更為refactory後的名稱才行。