google 一下 struts2、unit test,大概會出現「Struts 2 JUnit Plugin」,用它來做單元測試當然沒問題,但是,現在的人寫 JavaEE 誰不用 spring ? 既然已經是 spring + struts2,就不用那麼麻煩了! 這裡舉個簡單的例子,這是一個查詢特定選區各候選人得票統計的小程式,執行出來的畫面如下:
source code 已經放在 https://github.com/twleader/DemoSite/tree/master/MVCSite,有興趣的可以下載來看看。
針對程式裡的 AreaResultAction,我們應該如何進行單元測試? 說明如下:
- AreaResultAction.java
說明前,當然先看一下程式碼…
1 package idv.steven.demo.action; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Map; 6 import java.util.TreeMap; 7 8 import idv.steven.demo.dao.CandidateDAO; 9 import idv.steven.demo.dao.ElectionDAO; 10 import idv.steven.demo.dto.Candidate; 11 import idv.steven.demo.dto.Election; 12 13 import org.apache.struts2.convention.annotation.Action; 14 import org.apache.struts2.convention.annotation.Result; 15 import org.slf4j.Logger; 16 import org.slf4j.LoggerFactory; 17 import org.springframework.beans.factory.annotation.Autowired; 18 import org.springframework.stereotype.Controller; 19 20 import com.opensymphony.xwork2.ActionSupport; 21 22 @Controller 23 public class AreaResultAction extends ActionSupport { 24 private static final long serialVersionUID = -213699129575326012L; 25 final static Logger logger = LoggerFactory.getLogger(AreaResultAction.class); 26 27 @Autowired 28 private ElectionDAO electionDAO; 29 30 @Autowired 31 private CandidateDAO candidateDAO; 32 33 private Map<String, String> electionList = new TreeMap<String, String>(); 34 private Map<String, String> cityNameList = new TreeMap<String, String>(); 35 private Map<String, String> areaNameList = new TreeMap<String, String>(); 36 private List<Candidate> candidateList = new ArrayList<Candidate>(); 37 38 private String electionID; 39 private String cityName; 40 private String areaName; 41 42 /** 43 * 將網頁導向「AreaResult.jsp」(「選區投票結果查詢」網頁) 44 */ 45 @Action(value = "/areaResult", results = { @Result(location = "/jsp/AreaResult.jsp", name = "success") }) 46 public String execute() throws Exception { 47 return SUCCESS; 48 } 49 50 /** 51 * 將查詢結果顯示在 Grid 52 * @return 53 */ 54 @Action(value = "/showAreaResult", results = { @Result(location = "/jsp/AreaResultGrid.jsp", name = "success") }) 55 public String showAreaResult() { 56 return SUCCESS; 57 } 58 59 /** 60 * 傳回選舉場次 61 * @return 62 */ 63 @Action(value = "/findElection", results = { @Result(name = "input", type = "json") }) 64 public String findElection() { 65 List<Election> list = electionDAO.findAll(); 66 for(Election item:list) { 67 electionList.put(item.getId(), item.getName()); 68 } 69 70 return INPUT; 71 } 72 73 /** 74 * 傳回縣市 75 * @return 76 */ 77 @Action(value = "/findCityName", results = { @Result(name = "input", type = "json") }) 78 public String findCityName() { 79 List<String> list = candidateDAO.findCityName(electionID); 80 for(String item:list) { 81 cityNameList.put(item, item); 82 } 83 84 return INPUT; 85 } 86 87 /** 88 * 傳回選區 89 * @return 90 */ 91 @Action(value = "/findAreaName", results = { @Result(name = "input", type = "json") }) 92 public String findAreaName() { 93 List<String> list = candidateDAO.findAreaName(electionID, cityName); 94 for(String item:list) { 95 areaNameList.put(item, item); 96 } 97 98 return INPUT; 99 } 100 101 @Action(value = "/findCandidate", results = { @Result(name = "input", type = "json") }) 102 public String findCandidate() { 103 candidateList = candidateDAO.findCandidate(electionID, cityName, areaName); 104 105 return INPUT; 106 } 107 108 //由網頁中取值 109 public Map<String, String> getElectionList() { 110 return electionList; 111 } 112 113 public Map<String, String> getCityNameList() { 114 return cityNameList; 115 } 116 117 public Map<String, String> getAreaNameList() { 118 return areaNameList; 119 } 120 121 public List<Candidate> getCandidateList() { 122 return candidateList; 123 } 124 125 //網頁 submit 時傳值過來 126 public void setElectionID(String electionID) { 127 this.electionID = electionID; 128 } 129 130 public void setCityName(String cityName) { 131 this.cityName = cityName; 132 } 133 134 public void setAreaName(String areaName) { 135 this.areaName = areaName; 136 } 137 138 }
程式中的 findAreaName 是底下單元測試要測試的項目,至於 @Controller,其實並不是必要的,但是,加上去會有好處,加上之後,然後在 spring 設定檔中,透過 component-scan 讓它可以被 spring 自動載入。
<context:component-scan base-package="idv.steven.demo.dto,idv.steven.demo.action" />
上方的綠色部份,即是 struts action 所在的 package,全部的 action class 前都加上 @Controller 即可。
- 單元測試
1 package idv.steven.demo.action; 2 3 import static org.junit.Assert.*; 4 5 import java.util.Map; 6 7 import org.junit.After; 8 import org.junit.Before; 9 import org.junit.Test; 10 import org.junit.runner.RunWith; 11 import org.springframework.beans.factory.annotation.Autowired; 12 import org.springframework.test.context.ContextConfiguration; 13 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 14 15 import com.opensymphony.xwork2.ActionSupport; 16 17 @ContextConfiguration(locations={"classpath:beans-config.xml"}) 18 @RunWith(SpringJUnit4ClassRunner.class) 19 public class AreaResultActionTest { 20 @Autowired 21 private AreaResultAction action; 22 23 @Before 24 public void setUp() throws Exception { 25 26 } 27 28 @After 29 public void tearDown() throws Exception { 30 } 31 32 @Test 33 public void testFindAreaName() throws Exception { 34 action.setElectionID("201201"); 35 action.setCityName("高雄市"); 36 String result = action.findAreaName(); 37 assertEquals(ActionSupport.INPUT, result); 38 39 Map<String, String> map = action.getAreaNameList(); 40 assertEquals(9, map.size()); 41 } 42 }
上面是單元測試程式,測試 findAreaName 是否可以傳回預期的值,從程式碼可以看到,完全看不出是 web 程式,反正就用一般的 java class 的思考模式進行測試,line 34、35 將需要的參數傳入,然後執行要測試的 method (line 36),之後檢查傳回的結果是否正確! (line 37、40) 至於紅色部份就是前面強調的,在 action class 前加上 @Controller,在這裡就可以很方便的讓 spring 幫我們初始化並載入這個 class。