通常情况我们可以借助easyMock及powerMock进行单元测试,但有时我们希望进行集成测试,可以通过发送http请求,测试某功能的完整性。
一般情况我们可以通过MockMvc模拟post或get请求,完成测试。但是当碰到delete或update进行测试时,容易对数据库造成污染,这时我们可以借助dbunit,对数据库进行测试数据的准备,测试完成后对事务进行回滚,方便下次测试。
1. maven集成测试组件
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.10.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--数据库单元测试:消除单元测试对数据库的污染 start--> <dependency> <groupId>com.github.springtestdbunit</groupId> <artifactId>spring-test-dbunit</artifactId> <version>1.1.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.dbunit</groupId> <artifactId>dbunit</artifactId> <version>2.4.9</version> <scope>test</scope> </dependency> <!--数据库单元测试:消除单元测试对数据库的污染 end-->
2. 定义测试基类,包括SpringMVC框架的集成,junit集成
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @PropertySource("classpath:common.properties") @TestPropertySource("classpath:common.properties") @TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, ServletTestExecutionListener.class, DbUnitTestExecutionListener.class}) //@1 @ContextConfiguration( {"classpath*:/spring-context.xml", "classpath*:/spring-mvc.xml", "classpath*:/spring-mybatis.xml"}) @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)//@2 @Transactional public abstract class BaseSpringJUnitTest { public static Logger logger = LoggerFactory.getLogger(BaseSpringJUnitTest.class); @Autowired private UserInfoService userInfoService; private static boolean inited = false; /** * junit模拟用户名 */ private final static String USER_NAME = "admin"; /** * junit模拟验证码 */ private final static String VALIDATE_CODE = "1234"; /** * junit模拟密码 */ private final static String PASSWORD = "Admin123456"; public static String token = ""; protected MockMvc mockMvc; //@3 @Before //@4 public void setUp() throws Exception { if (!inited) { String code = userInfoService.getValidateKey().get("validateKey").toString(); RedisUtils.set("validCode:" + code, VALIDATE_CODE); UserInfoRequestParams params = new UserInfoRequestParams(); params.setLoginName(USER_NAME); params.setPassword(MD5Util.encoderHexByMd5(PASSWORD)); params.setValidateKey(code); params.setValidateCode(VALIDATE_CODE); JSONOutputObject result = userInfoService.webLogin(params); token = result.get("token").toString(); TestCase.assertEquals(RsmsConstant.RESULT_SUCCESS_CODE, result.get(RsmsConstant.RESULT_CODE)); inited = true; } } }
@1:ServletTestExecutionListener 用于设置spring web框架启动时的RequestContextHolder本地线程变量。我们的项目比较特殊,在service层中是通过RequestContextHolder获取httpRequest对象(并非通过controller透传),如果不设置,则在service中或者切面中无法获取到request对象
@2:添加事务回滚,避免对数据库的污染
@3:定义MockMvc对象,模拟客户端的http请求
@4:初始化登录信息,根据自身需要设置,有些集成测试场景可能需要登录信息
3.编写测试类
public class UserInfoControllerTest extends BaseSpringJUnitTest { @Test @DatabaseSetup(type = DatabaseOperation.INSERT, value = {"/data/user-info.xml"})//@1 public void testDeleteUsers() throws Exception { mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); //@2 String responseString = mockMvc.perform(post("/user/deleteUsers") .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE) .content("{\"idList\":10007}") .header("Current-Menu-Id", "102100") .header("Menu-Id", "102100") .accept(MediaType.ALL_VALUE) .header("token", token) .header("User-Agent", "Windows NT") ).andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); JSONObject jsonObject = JSONObject.parseObject(responseString); Assert.assertEquals(jsonObject.get("resultCode"), "0000"); } }
@1:准备测试数据,因为在测试时,如果不自己准备数据,依赖数据库数据,那么数据库数据有可能被其他人误删或者数据库做了迁移或更新之后,我们的测试用例将无法跑通。
@2:通过mockMvc发送http请求,并解析请求结果,判断测试结果的正确性
4.准备测试数据(resource/data/user_info.xml)
<?xml version="1.0" encoding="UTF-8" ?> <dataset> <t_user_info id="10007" login_name="admin_junit" password="" salt="" user_name="admin_junit" user_name_spell="" user_name_initial="" eid="" cellphone="" company_id="200" org_id="200" position=""/> <t_user_role id="1000" user_id="10007" role_id="1" /> </dataset>
5.jenkins集成,并统计单元测试覆盖率
<!-- 统计junit覆盖率 --> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.1</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>prepare-package</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin>
jenkins添加普通的maven构建任务,设置好git地址
插件管理中安装jacoco插件
build项设置如下
Root POM pom.xml
Goals and options test -Dmaven.repo.local=/opt/repository
构建后操作添加:Record JaCoCo coverage report
统计结果效果如下
单元测试覆盖率统计
6.配置邮件发送
邮件发送本人是集成了cobertura(和jacoco共一样,都是用于覆盖率的统计)
pom集成
<!-- cobertura统计junit覆盖率 --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.7</version> <configuration> <formats> <format>html</format> <format>xml</format> </formats> </configuration> </plugin>
jenkins全局配置:jenkins->系统管理->系统设置
系统管理员地址:*********@qq.com
Extended E-mail Notification中配置如下
SMTP:smtp.qq.com
后缀@qq.com
点开高级
使用SMTP认证
用户名:*********@qq.com
密码:qq给的授权码(非邮箱的登录密码,授权码的获取:登录QQ邮箱:设置-SMTP设置-开启,需要发送短信,发送短信后,页面会显示授权码)
jenkins构建任务配置邮件发送
构建后操作:增加Editable Email Notification,点开高级,一定要设置triggers,否则无法触发