一、單元測試的目的
簡單來說就是在我們增加或者改動一些代碼以后對所有邏輯的一個檢測,尤其是在我們后期修改后(不論是增加新功能,修改bug),都可以做到重新測試的工作。以減少我們在發布的時候出現更過甚至是出現之前解決了的問題再次重現。
這里主要是使用MockMvc對我們的系統的Controller進行單元測試。
對數據庫的操作使用事務實現回滾,及對數據庫的增刪改方法結束后將會還遠數據庫。
二、MockMvc的使用
1、首先我們上一個例子,
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.transaction.TransactionConfiguration; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Created by zhengcanrui on 16/8/11. */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:spring/applicationContext-*xml"}) //配置事務的回滾,對數據庫的增刪改都會回滾,便於測試用例的循環利用 @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true) @Transactional @WebAppConfiguration public class Test { //記得配置log4j.properties ,的命令行輸出水平是debug protected Log logger= LogFactory.getLog(TestBase.class); protected MockMvc mockMvc; @Autowired protected WebApplicationContext wac; @Before() //這個方法在每個方法執行之前都會執行一遍 public void setup() { mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc對象 } @org.junit.Test public void getAllCategoryTest() throws Exception { String responseString = mockMvc.perform( get("/categories/getAllCategory") //請求的url,請求的方法是get .contentType(MediaType.APPLICATION_FORM_URLENCODED) //數據的格式 .param("pcode","root") //添加參數 ).andExpect(status().isOk()) //返回的狀態是200 .andDo(print()) //打印出請求和相應的內容 .andReturn().getResponse().getContentAsString(); //將相應的數據轉換為字符串 System.out.println("--------返回的json = " + responseString); } }
Spring MVC的測試往往看似比較復雜。其實他的不同在於,他需要一個ServletContext來模擬我們的請求和響應。但是Spring也針對Spring MVC 提供了請求和響應的模擬測試接口,以方便我們的單元測試覆蓋面不只是service,dao層。
2、代碼解釋:
@webappconfiguration是一級注釋,用於聲明一個ApplicationContext集成測試加載WebApplicationContext。作用是模擬ServletContext
@ContextConfiguration:因為controller,component等都是使用注解,需要注解指定spring的配置文件,掃描相應的配置,將類初始化等。
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true) @Transactional
上面兩句的作用是,讓我們對數據庫的操作會事務回滾,如對數據庫的添加操作,在方法結束之后,會撤銷我們對數據庫的操作。
為什么要事務回滾?
- 測試過程對數據庫的操作,會產生臟數據,影響我們數據的正確性
- 不方便循環測試,即假如這次我們將一個記錄刪除了,下次就無法再進行這個Junit測試了,因為該記錄已經刪除,將會報錯。
- 如果不使用事務回滾,我們需要在代碼中顯式的對我們的增刪改數據庫操作進行恢復,將多很多和測試無關的代碼
方法解析:
- perform:執行一個RequestBuilder請求,會自動執行SpringMVC的流程並映射到相應的控制器執行處理;
- get:聲明發送一個get請求的方法。MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根據uri模板和uri變量值得到一個GET請求方式的。另外提供了其他的請求的方法,如:post、put、delete等。
- param:添加request的參數,如上面發送請求的時候帶上了了pcode = root的參數。假如使用需要發送json數據格式的時將不能使用這種方式,可見后面被@ResponseBody注解參數的解決方法
- andExpect:添加ResultMatcher驗證規則,驗證控制器執行完成后結果是否正確(對返回的數據進行的判斷);
- andDo:添加ResultHandler結果處理器,比如調試時打印結果到控制台(對返回的數據進行的判斷);
- andReturn:最后返回相應的MvcResult;然后進行自定義驗證/進行下一步的異步處理(對返回的數據進行的判斷);
注意事項:
- 在mac上使用log4j是,假如使用了${catalina.home}需要注意,mac不會去找到tomcat所在的路徑,直接回到根路徑 “/”,而正常情況下,根路徑是沒有寫權限的,需要使用管理員賦權限。
- log4j在配置完成之后,需要設置起打印日志的級別,假如沒有設置,在Junit中,將無法打印日志。
3、后台的返回數據中,最好帶上我們對數據庫的修改的結果返回的前端。
為什么要在data中返回一個修改或者添加的對象
- 將數據返回給前端,前端容易判斷數據是否添加或者修改成功
- 更新或者添加完數據經常需要刷新頁面,將數據直接給了前端,前端不用再發一個請求來獲取
- 單元測試的時候,能對數據庫的DDL(增刪改)操作的時候,我們能對數據進行審核,從何判斷我們的操作是否是成功的。如下面的例子:
我們發送一個添加操作,添加一個SoftInfo對象,SoftInfo類定義如下:
public class SoftInfo { private String id; private String name; }
添加完之后,由於我們進行了單元測試的事務回滾,我們將不能再數據庫中看我們我們的的添加操作,無法判斷操作是否成功。
為了解決上面的問題,我們可以在返回的json的數據中添加一個“data”字段,解析該json中的data字段數據,判斷我們的添加操作是否成功的。json格式如下:
{ "status":200, "data":{"id":"2","name":"測試"} }
我們可以使用andExpect方法對返回的數據進行判斷,用“$.屬性”獲取里面的數據,如我要獲取返回數據中的"data.name",可以寫成"$.data.name"。下面的例子是判斷返回的data.name=“測試”。
@Test public void testCreateSeewoAccountUser() throws Exception { mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_FORM_URLENCODED) ).andExpect(status().isOk()) .andExpect(jsonPath("$.data.name", is("測試")))) .andExpect(jsonPath("$.data.createTime", notNullValue())) ; }
三、遇到的問題
1、發送一個被@ResponseBody標識的參數,一直到400錯誤。 即無法發送一個json格式的數據到Controller層。
解決方法1:
SoftInfo softInfo = new SoftInfo();
//設置值
ObjectMapper mapper = new ObjectMapper(); ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter(); java.lang.String requestJson = ow.writeValueAsString(softInfo); String responseString = mockMvc.perform( post("/softs").contentType(MediaType.APPLICATION_JSON).content(requestJson)).andDo(print()) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
解決方法2:使用com.alibaba.fastjson.JSONObject將對象轉換為Json數據
SoftInfo softInfo = new SoftInfo(); //。。。設置值 String requestJson = JSONObject.toJSONString(folderInfo); String responseString = mockMvc.perform( post("/softs").contentType(MediaType.APPLICATION_JSON).content(requestJson)).andDo(print()) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
注意上面contentType需要設置成MediaType.APPLICATION_JSON,即聲明是發送“application/json”格式的數據。使用content方法,將轉換的json數據放到request的body中。
2、java.lang.NoClassDefFoundError: com/jayway/jsonpath/InvalidPathException
缺少了jar包:
可以添加一下的maven依賴
<dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <version>0.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path-assert</artifactId> <version>0.8.1</version> <scope>test</scope> </dependency>
學習鏈接:https://www.petrikainulainen.net/spring-mvc-test-tutorial/
致謝:感謝您的閱讀!