在項目中需要寫單元測試,如何保證寫的單元測試的質量是比較高的。有以下幾個原則。
- 編寫具有確定性結果的測試用例。
- 代碼中使用斷言,而不是System.out.print語句輸出結果,然後人工驗證。
- 對於需要訪問數據庫的操作或者外部數據,可以使用內存數據庫或者EasyMock之類的工具。
- 測試完數據之後,儘可能的恢復現場(測試之前的環境,這樣測試用例便可以重複執行)。
Spring集成TestNG
- 首先把需要的jar包加入到項目裏,因爲都是測試相關的,所以scope都是test,引入jar包的pom.xml需要增加如下的依賴(spring 的版本需要在3.2以上):
<dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>${testng.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> <!-- 用於記錄jdbc的日誌, 輸出的日誌格式會帶上相應的參數--> <dependency> <groupId>com.googlecode.log4jdbc</groupId> <artifactId>log4jdbc</artifactId> <version>1.2</version> <scope>test</scope> </dependency> <!-- H2內存數據庫, 適合用於處理測試用例的執行--> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.197</version> <scope>test</scope> </dependency>
- 編寫相應的測試用例。代碼如下:
import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import org.apache.tomcat.jdbc.pool.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; //@WebAppConfiguration:測試環境使用,用來表示測試環境使用的ApplicationContext將是WebApplicationContext類型的;value指定web應用的根; @WebAppConfiguration() //@ContextHierarchy:指定容器層次 @ContextHierarchy({ @ContextConfiguration(locations = { "classpath:applicationContext.xml" //這裏的applicationContext.xml文件,如果有特殊的bean需要配置,則需要放在src/test/resources目錄下 }), @ContextConfiguration({ "classpath:spring-mvc.xml" }) }) public class SysUserControllerTest extends AbstractTestNGSpringContextTests { //注入web環境的ApplicationContext容器 @Autowired private WebApplicationContext wac; private MockMvc mockMvc; //這裏可以執行初使化的數據腳本, 如果沒有,也可以不執行這個方法 SysUserControllerTest() { executeSql("sql/mysql/schema.sql"); executeSql("sql/mysql/import-data.sql"); } //BeforeClass會在testcase執行之前執行 @BeforeClass public void setUp() { //MockMvcBuilders.webAppContextSetup(wac).build()創建一個MockMvc進行測試 mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); } private void executeSql(String sqlPath) { DataSource dataSource = new DataSource(); dataSource.setDriverClassName("net.sf.log4jdbc.DriverSpy"); //採用這個driver 可以方便記錄jdbc的日誌 dataSource.setUrl("jdbc:log4jdbc:h2:mem:test;MODE=MySql;DB_CLOSE_DELAY=-1");//H2數據訪問的URL dataSource.setUsername("sa"); dataSource.setPassword(""); Connection connection = null; Statement st = null; try { connection = dataSource.getConnection(); // Thread.currentThread().getContextClassLoader().getResource(sqlPath) 得到的是以file:/開頭的路徑, 所以需要截取後6位的字符 String path = Thread.currentThread().getContextClassLoader().getResource(sqlPath).toString().substring(6); st = connection.createStatement(); st.execute("runscript from '" + path + "'"); } catch (Exception e) { e.printStackTrace(); } finally { if (st != null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } } @Test public void testadd() throws Exception { MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/sysUser/add.do?username=aaa&name=aaaa&password=aaaaaa&domainUsername=aaaa")). //perform用於執行一個請求 andDo(MockMvcResultHandlers.print()). //增加一個結果處理器 andExpect(MockMvcResultMatchers.status().isOk()). //執行完成後的斷言 andReturn(); //執行完成後返回相應的結果 String content = result.getResponse().getContentAsString(); JSONObject jsonObject = JSON.parseObject(content); //採用Asser的方式進行斷言 Assert.assertEquals(jsonObject.get("code"), "200"); } }
上面的代碼需要關注的點有下面幾個: 1: 如果spring的配置文件裏有bean的構造方式跟線上的不一致,需要在src/main/resources目錄下新建spring的配置文件,這樣testcase執行的時候加載的是測試環境的文件。比如數據庫的datasource bean就有可能不一樣。 2:在spring IOC容器之前如果有數據庫需要進行初使化的話,則可以在這個測試類的構造方法裏執行相應的代碼。 3:如果需要在spring IOC容器初使化之後執行相應的數據庫初使代碼,則可以在testng的@BeforeClass方法裏執行。 4:在測試具體的接口的時候,需要用斷言對結果進行預測。而不是打印相應的信息。 5:實際項目中可以參考使用H2內存數據庫,這樣寫的sql有什麼問題,測試用例也能夠儘快發現。 6:這樣寫的測試類會連同Spring MVC的基礎設施(如DispatcherServlet調度、類型轉換、數據綁定、攔截器, 最終渲染的視圖 @ResponseBody生成的JSON/XML、JSP、Velocity等)但是不會測試web.xml裏配置的filter