灵魂拷问:到底要不要写单元测试,如何正确进行单元测试?


prtyaa
prtyaa 2023-12-30 22:54:24 63385
分类专栏: 资讯

为什么要写单元测试

一聊起测试用例,很多人第一反应就是,我们公司的测试会写测试用例的,我自己也会使用postman或者swagger之类的进行代码自测。那我们研发到底要不要写单元测试用例呢?

参考阿里巴巴开发手册,第8条规则(单元测试的基本目标:语句覆盖率达到 70%;核心模块的语句覆盖率和分支覆盖率都要达到 100%),大厂的要求就是必须喽。我个人感觉,写单元测试用例也是很有必要的,好处很多,例如:

  • 保证代码质量!!!无论初级,中级,高级攻城狮开发工程的代码,且不说效率如何,功能是必要要保证是正确的;交付测试以后,bug锐减,联调飞快。
  • 代码逻辑“文档化”!!!新人接手维护模块代码时,通过单元测试用例,以debug的方式就能熟悉业务代码。比起,看代码,研究表结构梳理代码结构,效率提升飞快。
  • 易维护!!!新人接手维护代码模块时,提交自己的代码时,远行之前的单元测试达到回归测试,保证了新改动不会影响老业务。
  • 快速定位bug!!!在联调期间,测试提出bug后,基于uat环境,编写出错的api测试用例。根据,测试提供的参数和token就可以以debug的方式跟踪问题的所在,如果是在微服务架构中,运行单元测试用例,不会注册本地服务到uat环境,还能过正常请求注册中心的服务。

到底如何写单元测试

Java开发springboot项目都是基于junit测试框架,比较MockitoJUnitRunnerSpringRunner的使用,MockitoJUnitRunner基于mockito,模拟业务条件,验证代码逻辑。SpringRunner是MockitoJUnitRunner子类,集成了Spring容器,可以在测试的根据配置加载Spring bean对象。

在Springboot开发中,结合@SpringBootTest注解,加载项目配置,进行单元测试。

基于MockitoJUnitRunner的方法测试

以springboot项目为例,一般,对单个的方法都是进行mock测试,在测试方法使用MockitoJUnitRunner,根据不同条件覆盖测试。使用@InjectMocks注解,可以让模拟的方法正常发起请求;@Mock注解可以模拟期望的条件。以删除菜单服务为例,源码如下:

@Service
public class MenuManagerImpl implements IMenuManager {
 
    /**
     * 删除菜单业务逻辑
     **/
    @Override
    @OptimisticRetry
    @Transactional(rollbackFor = Exception.class)
    public boolean delete(Long id) {
 
        if (Objects.isNull(id)) {
            return false;
        }
        Menu existingMenu = this.menuService.getById(id);
        if (Objects.isNull(existingMenu)) {
            return false;
        }
        if (!this.menuService.removeById(id)) {
            throw new OptimisticLockingFailureException("删除菜单失败!");
        }
        return true;
    }
}
 
 
 /**
  * 删除菜单方法级单元测试用例
  **/
@RunWith(MockitoJUnitRunner.class)
public class MenuManagerImplTest {
 
    @InjectMocks
    private MenuManagerImpl menuManager;
 
    @Mock
    private IMenuService menuService;
 
    @Test
    public void delete() {
 
        Long id = null;
        boolean flag;
        // id为空
        flag = menuManager.delete(id);
        Assert.assertFalse(flag);
 
        // 菜单返回为空
        id = 1l;
        Mockito.when(this.menuService.getById(ArgumentMatchers.anyLong())).thenReturn(null);
        flag = menuManager.delete(id);
        Assert.assertFalse(flag);
 
        // 修改成功
        Menu mockMenu = new Menu();
        Mockito.when(this.menuService.getById(ArgumentMatchers.anyLong())).thenReturn(mockMenu);
        Mockito.when(this.menuService.removeById(ArgumentMatchers.anyLong())).thenReturn(true);
        flag = menuManager.delete(id);
        Assert.assertTrue(flag);
    }
}

基于SpringRunner的Spring容器测试

在api开发过程中,会对单个api的调用链路进行验证,对第三方服务进行mock模拟,本服务的业务逻辑进行测试。

一般,会使用@SpringBootTest加载测试环境的Spring容器配置,使用MockMvc以http请求的方式进行测试。以修改新增菜单测试用例为例,如下:

/**
 * 成功新增菜单api
*/
@Api(tags = "管理员菜单api")
@RestController
public class AdminMenuController {
 
    @Autowired
    private IMenuManager menuManager;
 
 
    @PreAuthorize("hasAnyAuthority('menu:add','admin')")
    @ApiOperation(value = "新增菜单")
    @PostMapping("/admin/menu/add")
    @VerifyLoginUser(type = IS_ADMIN, errorMsg = INVALID_ADMIN_TYPE)
    public Response<MenuVo> save(@Validated @RequestBody SaveMenuDto saveMenuDto) {
        return Response.success(menuManager.save(saveMenuDto));
    }
}
 
 
 
/**
 * 成功新增菜单单元测试用例
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MallSystemApplication.class)
@Slf4j
@AutoConfigureMockMvc
public class AdminMenuControllerTest extends BaseTest {
 
/**
 * 成功新增菜单
*/
@Test
public void success2save() throws Exception {
 
        SaveMenuDto saveMenuDto = new SaveMenuDto();
        saveMenuDto.setName("重置密码");
        saveMenuDto.setParentId(1355339254819966978l);
        saveMenuDto.setOrderNum(4);
        saveMenuDto.setType(MenuType.button.getValue());
        saveMenuDto.setVisible(MenuVisible.show.getValue());
        saveMenuDto.setUrl("https:baidu.com");
        saveMenuDto.setMethod(MenuMethod.put.getValue());
        saveMenuDto.setPerms("user:reset-pwd");
        // 发起http请求
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
                .post("/admin/menu/add")
                .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
                .content(JSON.toJSONString(saveMenuDto))
                .accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
                .header(GlobalConstant.AUTHORIZATION_HEADER, GlobalConstant.ADMIN_TOKEN))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
        Response<MenuVo> response = JSON.parseObject(mvcResult.getResponse().getContentAsString(), menuVoTypeReference);
        // 断言结果
        Assert.assertNotNull(response);
        MenuVo menuVo;
        Assert.assertNotNull(menuVo = response.getData());
        Assert.assertEquals(menuVo.getName(), saveMenuDto.getName());
        Assert.assertEquals(menuVo.getOrderNum(), saveMenuDto.getOrderNum());
        Assert.assertEquals(menuVo.getType(), saveMenuDto.getType());
        Assert.assertEquals(menuVo.getVisible(), saveMenuDto.getVisible());
        Assert.assertEquals(menuVo.getStatus(), MenuStatus.normal.getValue());
        Assert.assertEquals(menuVo.getUrl(), saveMenuDto.getUrl());
        Assert.assertEquals(menuVo.getPerms(), saveMenuDto.getPerms());
        Assert.assertEquals(menuVo.getMethod(), saveMenuDto.getMethod());
    }
}

具体编写单元测试用例规则参考测试用例的编写。简单说,一般api的单元测试用例,编写两类,如下:

  • 业务参数的校验,和义务异常的校验。例如,名称是否为空,电话号码是否正确,用户未登陆则抛出未登陆异常。
  • 各类业务场景的真实测试用例,例如,编写成功添加顶级菜单的测试用例,已经编写成功添加子级菜单的测试用例。

注意事项

配置覆盖

此外,如上基于mockmvc的编写的测试用例,由于加载了Spring的配置,会对项目发起真实的调用。如果,环境的配置为线上配置,容易出现安全问题。

一般出于安全考虑,很多公司会对真实环境的修改操作做事务回滚操作,甚至根本就不会进行真实环境的调用,使用模拟环境替换,例如数据库的操作可以使用h2内存数据库进行替换。

这时,可以在src/test/resources目录下,添加与src/main/resources目录下,相同的文件进行配置覆盖。src/test/main目录下的代码,会首先加载src/test/resources目录下的配置,如果没有则在加载src/main/resources目录的配置。常用场景如下:

  • 在单元测试环境使用使用内存数据库
  • ginkens代码集成运行测试用例时,不希望在集成环境中输出日志文件信息,并且以debug级别输出日志。

以日志文件配置覆盖为例,在src/main/resources目录下配置日志有文件和控制台输出,如图:

main/resource目录下的logback-spring.xml,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
 
 <contextName>mall-system</contextName>
 
    <!-- 控制台日志输出配置 -->
 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
   <pattern>
    [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level][%thread] [%contextName] [%logger{80}:%L] %msg%n
   </pattern>
   <charset>UTF-8</charset>
  </encoder>
  <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
   <level>DEBUG</level>
  </filter>
 </appender>
 
    <!-- 日志文件输出配置 -->
 <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <file>log/info.log</file>
  <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
   <fileNamePattern>log/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
   <maxFileSize>50MB</maxFileSize>
   <maxHistory>50</maxHistory>
   <totalSizeCap>10GB</totalSizeCap>
  </rollingPolicy>
  <encoder>
   <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level] [%contextName] [%logger{80}:%L] %msg%n</pattern>
  </encoder>
 </appender>
 
    <!-- 设置INFO 级别输出日志 -->
 <root level="INFO">
  <appender-ref ref="STDOUT" />
  <appender-ref ref="FILE" />
 </root>
</configuration>

src/test//resource目录下的新增logback-spring.xml,去掉日志文件输出的配置,设置日志输出级别为DEBUG;如果运行测试用例,则加载该配置不会进行日志文件的输出,并且打印DEBUG级别日志。如图:

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
 
    <contextName>mall-system</contextName>
    
    <!-- 控制台日志输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level][%thread] [%contextName] [%logger{80}:%L] %msg%n
            </pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
    </appender>
 
    <!-- DEBUG级别日志输出 -->
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

指定环境

一般开发过程中,我们研发只会操作开发环境,也是为了避免数据安全问题,可以在单元测试用例中指定运行的环境配置。在测试类加上@ActiveProfiles("dev"),指定获取dev环境的配置。示例,

/**
* 获取dev环境配置
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MallSystemApplication.class)
@Slf4j
@AutoConfigureMockMvc
@ActiveProfiles("dev")
public class AdminMenuControllerTest extends BaseTest {
}

在联调测试中,对于出错的api,可以编写对应的单元测试用例,使用@ActiveProfiles("uat")指定到测试环境,就可以根据测试提供的参数快速定位问题。示例:

/**
 * 新增菜单api联调
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MallSystemApplication.class)
@Slf4j
@AutoConfigureMockMvc
@ActiveProfiles("uat")
public class AdminMenuControllerTest extends BaseTest {
 
/**
 * 成功新增菜单
*/
@Test
public void success2save() throws Exception {
 
String token="Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjhjMjhlZWEzLTA5MWEtNDA1OS1iMzliLTRjOGMyNGY4ZjEzMiJ9.xK9srWjeGaq4NXt4BzG2MQ_yN9IaYtPVjKj5MoSS4bX9Ytf1XJNe_NSupR0IItkB48G6mXVZwj5CIwWIYzvsEA";
    String paramJson="{
        "name":"mayuan",
        "parentId":"1",
        "orderNum":"1",
        "type":"1",
        "visible":true,
        "url":"https:baidu.com",
        "method":2,
        "perms":"user:reset-pwd"
   }";
   // 发起http请求
   MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
                .post("/admin/menu/add")
                .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
                .content(paramJson)
                .accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
                .header(GlobalConstant.AUTHORIZATION_HEADER, token))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }
}

网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。

本文链接:https://www.xckfsq.com/news/show.html?id=32916
赞同 0
评论 0 条
prtyaaL1
粉丝 1 发表 2554 + 关注 私信
上周热门
银河麒麟添加网络打印机时,出现“client-error-not-possible”错误提示  1489
银河麒麟打印带有图像的文档时出错  1407
银河麒麟添加打印机时,出现“server-error-internal-error”  1196
统信操作系统各版本介绍  1118
统信桌面专业版【如何查询系统安装时间】  1116
统信桌面专业版【全盘安装UOS系统】介绍  1071
麒麟系统也能完整体验微信啦!  1029
统信【启动盘制作工具】使用介绍  674
统信桌面专业版【一个U盘做多个系统启动盘】的方法  618
信刻全自动档案蓝光光盘检测一体机  529
本周热议
我的信创开放社区兼职赚钱历程 40
今天你签到了吗? 27
信创开放社区邀请他人注册的具体步骤如下 15
如何玩转信创开放社区—从小白进阶到专家 15
方德桌面操作系统 14
我有15积分有什么用? 13
用抖音玩法闯信创开放社区——用平台宣传企业产品服务 13
如何让你先人一步获得悬赏问题信息?(创作者必看) 12
2024中国信创产业发展大会暨中国信息科技创新与应用博览会 9
中央国家机关政府采购中心:应当将CPU、操作系统符合安全可靠测评要求纳入采购需求 8

添加我为好友,拉您入交流群!

请使用微信扫一扫!