Login  |  繁體中文
感謝您對「自由軟體鑄造場」的支持與愛護,十多年來「自由軟體鑄造場」受中央研究院支持,並在資訊科學研究所以及資訊科技創新研究中心執行,現已完成階段性的任務。 原網站預計持續維運至 2021年底,網站內容基本上不會再更動。本網站由 Denny Huang 備份封存。
也紀念我們永遠的朋友 李士傑先生(Shih-Chieh Ilya Li)。
Previous Issue

jMock

Java Opensources for Web Development Part I:
Chapter 4 在 Java 中進行各種單元測試
Lession 16 : jMock

* Category: Test
* Project Name: jMock
* WebSite: https://www.jmock.org/
* License: jMock project license
* Last version: 2.0.0 stable, 2.1.0 (rc3)

當施行 Test Driven Development 的時候,我們未必會有實體的環境與資料源可以進行測試,所以我們必須模擬部分環境,與資料回應的狀態,如果僅僅使用 JUnit,我們產出的資料其實是回應在每個實作的 Object class 之中。

例如 UserDAOImpl 是撰寫 存取資料庫的實作。

package com.softleader.unittest;
public class UserDAOImpl implements UserDAO{
 public UserDAOImpl(){
  // 連結資料庫
 }
 public void createUser(User user) {
  // insert into user ( id, name ) values ( $id, $name );
  
 }
 public User getUser(long id) {
  // select * from user where id = $id;
  return null;
 }
 public void removeUser(long id) {
  // delete user where id = $id
  
 }
 public void setUser(User user) {
  // update user set name = $name where id = $id
  
 } 
}

User 是一個簡單的 ValueObject,目前設定簡單的屬性有 id 與 name。

package com.softleader.unittest;

import org.apache.commons.lang.builder.ToStringBuilder;

public class User {
 private long id;
 private String name;
 public long getId() {
  return id;
 }
 public void setId(long id) {
  this.id = id;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 
 public String toString() {
  return ToStringBuilder.reflectionToString(this);
 }
}

如果僅使用 JUnit 測試 getUser(), 我們就需要把 UserDAOImpl 撰寫成為

 public User getUser(long id) {
  User user = new User();
  user.setId(1);
  user.setName("jini");
  
  return user;
 }

不過,這方法在未來上線之前,變得還需要去修改 UserDAOImpl 轉為存取資料庫,所以我們會希望能夠在測試環境中,利用 interface 的自我實作方式,來進行模擬測試。

UserBusinessDelegate 是一支呼叫 DAO 的程式,我們可以把模擬物件的 DAO 放到 Delegate 之中,讓 Delegate 進行模擬測試。在實際環境時,則是將真實的 DAO inject進來即可,這也是 IoC 的重要應用之一。

package com.softleader.unittest;

public class UserBusinessDelegate {
 private UserDAO dao;
 public void setDAO(UserDAO dao) {
  this.dao = dao;
 }
 public void deleteUser(long id) {
  dao.removeUser(id);
 }
 public User getUser(long id) {
  return dao.getUser(id);
 }
 public void insertUser(User user) {
  dao.createUser(user);
 }
 public void updateUser(User user) {
  dao.setUser(user);
 }
}

package com.softleader.unittest;

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Test;

public class UserBussinessDelegateTest {
 // Mockery 初始化
 Mockery context = new JUnit4Mockery();
  
    @Test
    public void testGetUser() {
     // 利用 context 呼叫 UserDAO interface 產生模擬物件
     final UserDAO dao = context.mock(UserDAO.class);
     // 將 模擬 DAO 放到 Delegate 之中
     UserBusinessDelegate delegate = new UserBusinessDelegate();
     delegate.setDAO(dao);
     // 建立一個模擬物件
     final User testuser = new User();
     testuser.setId(1);
     testuser.setName("jini");
     
        // 設定期望的處理情況
        context.checking(new Expectations() {{
            one (dao).getUser(1);           
            will(returnValue(testuser));
        }});
     
     System.out.println(delegate.getUser(1));
    }
}

最後就會印出
com.softleader.unittest.User@929206[id=1,name=jini]
換句話說,我們利用 jMock 創造出一個 User模擬物件 [id=1, name=jini],在 delegate 呼叫 DAO 的時候,就會回傳該物件。

在 jMock 的實作之中,比較簡單的回傳值,就直接使用 returnValue 即可,但是如果是回傳多筆資料,就需要利用 returnIterator() 的方式,更特殊的情況有可能是不同次呼叫時,回有不同的回傳值,就可以利用 onConsecutiveCalls(returnValue(“firsttime”), returnValue(“secondtime”), returnValue(“thirdtime”)) 的方式來設定依序的回傳值。

回傳的情況除了數值與物件或是集合之外,當然也有可能是例外狀況。僅需要簡單地使用 will(throwException(new SomeException(“you get an exception”))); 的方式來檢測例外狀況是否發生。

jMock 支援許多呼叫的方式,可以參考 jMock Cookbook (https://jmock.codehaus.org/cookbook.html ) 仔細研究之即可。

另外,在 SpringFramework 之中應用 jMock 可以參考

https://appfuse.dev.java.net/TDDWithAppFuse.pdf




OSSF Newsletter : 第 80 期 Sun 釋出 Java SE 源碼

Category: Tech Column