在项目中需要写单元测试,如何保证写的单元测试的质量是比较高的。有以下几个原则。
- 编写具有确定性结果的测试用例。
- 代码中使用断言,而不是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
mockit使用链接:http://www.cnblogs.com/simplestupid/p/5170220.html