傳入到Method的參數,型態為底層的Interface。
以Spring 3.0 MVC 為例:
假設有一個RentController,該Controler 有一個 tenantPage() Method ,傳入Metod 的參數皆為底層的Interface 。程式碼如下:
@Controller
@RequestMapping("/rent")
public class RentController {
@Autowired
private IRentService service;
@RequestMapping(value = "/tenant", method = RequestMethod.POST)
public String tenantPage(Model model,HttpServletRequest request) {
Integer id=Integer.valueOf(request.getParameter("id"));
Tenant tenant=service.getTenant(id);
model.addAttribute("tenant", tenant);
return "tenant";
}
public void setService(IRentService service) {
this.service = service;
}
}
其中Method 參數: model 的型態為org.springframework.ui.Model;request 的型態為javax.servlet.http.HttpServletRequest。 二個參數型態都是Interface,當撰寫Unit Test時會出現一個狀況,必需要實作這些Interface才能將參數傳入。為了測試而去實作這些複雜的Interface是沒有效率的,如果下次碰到不同的Interface 又要再實作一次,那就不會有人想再去寫Unit Test 了。
@Test
public void testRentController(){
RentController rc=new RentController();
rc.setService(new RentServiceImpl());
//model 和 HttpServletRequest 都是Interface,new 不出來
Model model=yourModelImpl();
HttpServletRequest request=yourRequestImpl();
String pageName=rc.tenantPage(model, request);
Assert.assertEquals("tenant",pageName);
}
所以當測試遇到底層的Interface 時,可以有2種作法。
第1種作法: 如果Methd中的核心邏輯不會和底層的Interface有相依性,則可將此核心邏輯抽取出來成一個Method,Unit Test就針對這個Method 來測試。例如:在 tenantPage()中,service.getTenant(id)是主要的邏輯且沒有和底層Interface有交互作用,因此可將 service.getTenant(id) 抽取出一個Method 名為getTenant(Integer id)。這時getTenant(Integer id)就很單純 ,只有一個型態為Integer 的id,相對的也比較好進行 Unit Test 。
第1種作法: 如果Methd中的核心邏輯不會和底層的Interface有相依性,則可將此核心邏輯抽取出來成一個Method,Unit Test就針對這個Method 來測試。例如:在 tenantPage()中,service.getTenant(id)是主要的邏輯且沒有和底層Interface有交互作用,因此可將 service.getTenant(id) 抽取出一個Method 名為getTenant(Integer id)。這時getTenant(Integer id)就很單純 ,只有一個型態為Integer 的id,相對的也比較好進行 Unit Test 。
@RequestMapping(value = "/tenant", method = RequestMethod.POST)
public String tenantPage(Model model,HttpServletRequest request) {
Integer id=Integer.valueOf(request.getParameter("id"));
//Tenant tenant=service.getTenant(id);
model.addAttribute("tenant", getTenant(id));
return "tenant";
}
//new extract method
public Tenant getTenant(Integer id){
return service.getTenant(id);
}
第2種作法: 如果核心邏輯無法和底層的Interface做有效切割,這時就只能使用Mock Framework。
利用Mock 來模擬底層物件,而不必自己辛苦去實作這些Interface。有關Mock的測試方法,未來會再詳細述說。以下的測試碼是使用EasyMock Framework,來模擬Model 和 HttpServletRequest。
利用Mock 來模擬底層物件,而不必自己辛苦去實作這些Interface。有關Mock的測試方法,未來會再詳細述說。以下的測試碼是使用EasyMock Framework,來模擬Model 和 HttpServletRequest。
public class RentControllerTest {
private RentController rc;
private RentServiceImpl mockService;
@Before
public void setUp() {
rc = new RentController();
mockService = createMock(RentServiceImpl.class);
rc.setService(mockService);
}
@Test
public void testRentController() {
//test prepare
String key = "id";
String idVal = "2";
String attName = "tenant";
Tenant tenant = getTenant();
HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
Model mockModel = createMock(Model.class);
//expect mock behavior
expect(mockRequest.getParameter(key)).andReturn(idVal);
expect(mockService.getTenant(Integer.valueOf(idVal))).andReturn(tenant);
expect(mockModel.addAttribute(attName, tenant)).andReturn(mockModel);
//replay mock behavior
replay(mockRequest);
replay(mockService);
replay(mockModel);
//action start
String pageName = rc.tenantPage(mockModel, mockRequest);
//verify mock behavior
verify(mockRequest);
verify(mockService);
verify(mockModel);
//assert test result
Assert.assertEquals(attName, pageName);
}
private Tenant getTenant() {
Tenant tenant = new Tenant(Identity.GENERAL);
tenant.setAge(31);
tenant.setCareer("IT");
tenant.setName("John");
return tenant;
}
}
使用Mock就可以把複雜的介面切開,而且也可以擺脫環境的限制。上例中的測試碼包含了許多Expect ,其目的是要檢查是否有執行到這段程式碼。所以,Mock就相當於白箱測試,白箱測試會比黑箱測試複雜,你看測試碼是不是變得比較多了呢!
沒有留言:
張貼留言