1、Junit 单元测试
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。JUnit 5由三个不同子项目中的几个不同模块组成
JUnit Platform是基于JVM的运行测试的基础框架在,它定义了开发运行在这个测试框架上的TestEngine API。此外该平台提供了一个控制台启动器,可以从命令行启动平台,可以为Gradle和 Maven构建插件,同时提供基于JUnit 4的Runner。
JUnit Jupiter是在JUnit 5中编写测试和扩展的新编程模型和扩展模型的组合.Jupiter子项目提供了一个TestEngine在平台上运行基于Jupiter的测试。
JUnit Vintage提供了一个TestEngine在平台上运行基于JUnit 3和JUnit 4的测试。
常用注解
@Test 表示方法是一种测试方法。 与JUnit 4的@Test注解不同,此注释不会声明任何属性。
@BeforeEach 表示方法在每个测试方法运行前都会运行 ,@AfterEach 表示方法在每个测试方法运行之后都会运行
@BeforeAll 表示方法在所有测试方法之前运行 ,@AfterAll 表示方法在所有测试方法之后运行
2、MockMvc 接口测试
MockMvc是由spring-test包提供,实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,使得测试速度快、不依赖网络环境。
@MockBean:用于mock指定的class或被注解的属性。
@MockBeans:使@MockBean支持在同一类型或属性上多次出现。
@SpyBean:用于spy指定的class或被注解的属性。
@SpyBeans:使@SpyBean支持在同一类型或属性上多次出现
MockBean和SpyBean功能非常相似,都能模拟方法的各种行为。不同之处在于MockBean是全新的对象,跟正式对象没有关系;而SpyBean与正式对象紧密联系,可以模拟正式对象的部分方法,没有被模拟的方法仍然可以运行正式代码。@MockBean
和@SpyBean
这两个注解,在mockito框架中本来已经存在,且功能基本相同。Spring Boot Test又定义一份重复的注解,目的在于使MockBean
和SpyBean
被ApplicationContext管理,从而方便使用。下面对service与controller进行示例:
定义loginservice
@Service
public class LoginServiceImpl implements LoginService {
@Override
public String login(String loginName, String passWord){
if("admin".equals(loginName) && "admin".equals(passWord)){
return "{\"code\":\"0\",\"msg\":\"success\"}";
}
return "{\"code\":\"1\",\"msg\":\"fail\"}";
}
@Override
public String getInfo(String loginName){
if(!"admin".equals(loginName)){
return null;
}
Map<String,Object> map = new HashMap<>();
map.put("permit", Arrays.asList("a","b","c"));
map.put("info", Arrays.asList("b","a","c"));
return JSON.toJSONString(map);
}
@Override
public String getMenu(List<String> permits){
if(CollectionUtils.isEmpty(permits)){
return null;
}
Map<String,Object> map = new HashMap<>();
map.put("menus", Arrays.asList("a","b","c"));
return JSON.toJSONString(map);
}
}
loginservice测试用例:
@SpringBootTest
class LoginServiceTest {
@Autowired
private LoginService userService;
/**
* 登录测试
*/
@Test
void loginTest(){
String res = userService.login("admin","admin");
JSONObject jObj = JSON.parseObject(res,JSONObject.class);
Integer num = jObj.getAsNumber("code").intValue();
Assertions.assertEquals(0,num);
}
/**
* 获取登录信息测试
*/
@Test
void getInfoTest(){
String infoRes = userService.getInfo("admin");
Assert.notNull(infoRes,"getInfo结果不能为空");
Map<String,Object> map = JSON.parseObject(infoRes,Map.class);
Assert.notEmpty((Collection<?>) map.get("permit"),"权限列表不能为空");
Assert.notEmpty((Collection<?>) map.get("info"),"信息列表不能为空");
}
/**
* 获取菜单信息测试
*/
@Test
void getMenuTest(){
String infoRes = userService.getMenu(Arrays.asList("a","b","c"));
Map<String,Object> map = JSON.parseObject(infoRes,Map.class);
Assert.notNull(infoRes,"getMenu结果不能为空");
Assert.notEmpty((Collection<?>) map.get("menus"),"菜单列表不能为空");
}
}
定义userservice及usercontroller
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public void insert(User user) {
super.save(user);
}
@Override
public void delete(Long id) {
super.removeById(id);
}
@Override
public void update(User user) {
super.updateById(user);
}
@Override
public User get() {
return new User();
}
}
@RestController
public class UserController {
@Resource
private LoginService loginService;
@Resource
private UserService userService;
@GetMapping("/api/login")
public String login(
@RequestParam(required = true,name = "userName")String userName,
@RequestParam(required = true,name = "passWord")String passWord
){
return loginService.login(userName,passWord);
}
@GetMapping("/api/getLoginInfo")
public String getLoginInfo(
@RequestParam(required = true,name = "userName")String userName
){
return loginService.getInfo(userName);
}
@GetMapping("/api/get")
public User get(){
return userService.get();
}
@PostMapping("/api/insert")
public String insert(@RequestBody UserDto dto){
User user = new User();
BeanUtils.copyProperties(dto,user);
user.setDeleted(1);
userService.insert(user);
return "success";
}
@PutMapping("/api/update")
public String update(@RequestBody UserDto dto){
User user = new User();
BeanUtils.copyProperties(dto,user);
userService.update(user);
return "success";
}
@DeleteMapping("/api/delete/{id}")
public String delete(@PathVariable Long id){
userService.delete(id);
return "success";
}
}
对接口进行测试:
@AutoConfigureMockMvc
@SpringBootTest
class UserMvcTest {
@Autowired
private MockMvc mockMvc;
@Test
void loginApiTest() throws Exception {
//表单接口 测试
MvcResult result = mockMvc
.perform(MockMvcRequestBuilders.get("/api/login")
//表单 类型
.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
//参数
.param("userName","admin")
.param("passWord","admin"))
//打印响应信息
.andDo(MockMvcResultHandlers.print())
.andReturn();
String res = result.getResponse().getContentAsString();
Assert.notNull(res,"登录结果不能为空!");
//验证 结果
JSONObject jObj = JSON.parseObject(res,JSONObject.class);
Integer num = jObj.getAsNumber("code").intValue();
Assertions.assertEquals(0,num);
}
@Test
void insertTest() throws Exception{
//测试数据
UserDto dto = new UserDto();
dto.setAge(12);
dto.setName("张三");
//json 接口测试
MvcResult result = mockMvc
.perform(MockMvcRequestBuilders.post("/api/insert")
//json 类型
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
//参数
.content(JSON.toJSONString(dto)))
//打印响应信息
.andDo(MockMvcResultHandlers.print())
.andReturn();
String res = result.getResponse().getContentAsString();
Assert.notNull(res,"添加结果不能为空!");
//验证 结果
Assertions.assertEquals("success",res);
}
}
对接口进行模拟测试:
@SpringBootTest
@AutoConfigureMockMvc
class UserMockTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void searchTest() throws Exception {
//构造mock
User user = new User();
user.setId(1L);
user.setDeleted(1);
user.setAge(12);
user.setName("李四");
when(userService.get()).thenReturn(user);
//接口测试
MvcResult result = mockMvc
.perform(MockMvcRequestBuilders.get("/api/get"))
.andDo(print())
.andExpect(status().isOk())
.andReturn();
String res = result.getResponse().getContentAsString();
User resUser = JSON.parseObject(res,User.class);
Assert.notNull(resUser,"返回用户信息不能为空!");
}
@Test
void insertTest() throws Exception {
User user = new User();
user.setId(1L);
user.setDeleted(1);
user.setAge(12);
user.setName("李四");
doNothing().when(userService).insert(user);
UserDto dto = new UserDto();
BeanUtils.copyProperties(user,dto);
//接口测试
MvcResult result = mockMvc
.perform(MockMvcRequestBuilders.post("/api/insert")
//json 类型
.contentType(MediaType. APPLICATION_JSON_VALUE)
//参数
.content(JSON.toJSONString(dto)))
//打印响应信息
.andDo(MockMvcResultHandlers.print())
.andReturn();
String res = result.getResponse().getContentAsString();
Assert.notNull(res,"添加结果不能为空!");
//验证 结果
Assertions.assertEquals("success",res);
}
}
3、SonarQube 质量保障测试
SonarQube 是一种主流的代码质量持续检测工具。您可以将其用于代码库的静态和动态分析。SonarQube 集成到 KubeSphere 流水线后,如果在运行的流水线中检测到问题,您可以直接在仪表板上查看常见代码问题,例如 Bug 和漏洞。
添加sonarqube插件
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.7.0.1746</version>
</plugin>
maven中添加sonarqube group和服务地址
<pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
<profile>
<id>sonar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<!-- 配置 Sonar Host地址,默认:http://localhost:9000 -->
<sonar.host.url>
http://host:9000
</sonar.host.url>
</properties>
</profile>
修改sonar插件命令添加token即可运行
sonar:sonar -Dsonar.login=token -f pom.xml
提供一个示例项目以供参考:
https://github.com/wenxpro/test-demo
参考资料
https://junit.org/junit5/docs/current/user-guide
https://spring.io/guides/gs/testing-web
https://docs.sonarqube.org/latest
https://www.cnblogs.com/shawwey/p/10722391.html
https://kubesphere.io/zh/docs/v3.3/devops-user-guide/how-to-integrate/sonarqube/
-end-