本文基于SpringMVC搭建测试框架
通常情况我们可以借助easyMock及powerMock进行单元测试,但有时我们希望进行集成测试,可以通过发送http请求,测试某功能的完整性。
一般情况我们可以通过MockMvc模拟post或get请求,完成测试。但是当碰到delete或update进行测试时,容易对数据库造成污染,这时我们可以借助dbunit,对数据库进行测试数据的准备,测试完成后对事务进行回滚,方便下次测试。
1. maven集成测试组件
org.springframework
spring-test
4.3.10.RELEASE
junit
junit
4.12
test
com.github.springtestdbunit
spring-test-dbunit
1.1.0
test
org.dbunit
dbunit
2.4.9
test
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)
5.jenkins集成,并统计单元测试覆盖率
org.jacoco
jacoco-maven-plugin
0.8.1
prepare-agent
report
prepare-package
report
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集成
org.codehaus.mojo
cobertura-maven-plugin
2.7
html
xml
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,否则无法触发
邮件发送我用的是jelly模板,使用的时候只需要填写模板文件名称(.jelly后缀不需要),模板文件放在jenkins服务器上,具体模板代码见附件一,见下图
7.邮件发送最终效果
邮件主要内容:构建状态、覆盖率整体情况、包覆盖率情况、UT趋势、变更集等等
附件一:jelly邮件模板
${project.name}
BUILD ${build.result}
构建地址
${rooturl}${build.url}
项 目:
${project.name}
构建日期:
${it.timestampString}
构建时长:
${build.durationString}
Build cause:
${cause.shortDescription}
Build description:
${build.description}
Built on:
${build.builtOnStr}
master
Cobertura报告
趋势
项目覆盖率汇总
Name
${metric.name}
${coberturaResult.name}
${coberturaResult.getCoverage(metric).percentage}%(${coberturaResult.getCoverage(metric)})
Source
${coberturaResult.relativeSourcePath}
${coberturaResult.sourceFileContent}
Source code is unavailable
Coverage Breakdown by ${element.displayName}
Name
${metric.name}
${child.xmlTransform(child.name)}
${childResult.percentage}%(${childResult})
N/A
健康报告
W
描述
分数
${healthReport.description}
${healthReport.score}
变更集
${cs.msgAnnotated}
by
${cs.author}
${spc}${p.editType.name}
${p.path}
无
Build Artifacts
单元测试
包路径
失败
通过
跳过
总计
${packageResult.getName()}
${packageResult.getFailCount()}
${packageResult.getPassCount()}
${packageResult.getSkipCount()}
${packageResult.getPassCount()+packageResult.getFailCount()+packageResult.getSkipCount()}
${failed_test.getFullName()}
Static Analysis Results
Name
Result
Total
High
Normal
Low
${action.displayName}
${action.result.numberOfAnnotations}
${action.result.getNumberOfAnnotations('HIGH')}
${action.result.getNumberOfAnnotations('NORMAL')}
${action.result.getNumberOfAnnotations('LOW')}