欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot——单元测试1——业务逻辑层单元测试

程序员文章站 2022-04-26 16:04:33
...

引入Spring-test-starter即可引入
Junit+Mockito+assertJ

业务逻辑层的单元测试

分析业务逻辑层的职责:
被controller层进行调用后;
1.校验数据
2.执行业务逻辑
- 调用其他原子服务的接口
- 组装数据
- 执行对应的操作
3.执行数据库操作
举个例子:创建订单createOrder()

public class OrderService {
	public Boolean createOrder(CreateOrderRequest req) {
	//校验参数
	checkOrderRequestParamter(req);
	//获得当前用户具体信息:如用户名,用户手机号等信息
	//如果是微服务,则需要调用http接口或者是调用对应dubbo服务
	UserMsg userMsg = userFacade.getUser(req.getUserId);
	fillOrderUserMsg(req,userMsg);
	//获取对应商品信息
	List<GoodMsg> goods = GoodsFacade.getGoods(req.getItemsSkuSn());
	//对商品信息的有效性进行校验,并填充到req中
	checkAndFillGoodsMsg(goods,req);
	//计算订单商品金额等信息
	calculatedAmount(req);
	/**还有些业务逻辑**/
	//落库
	orderMapper.createOrder(req);
}
	
}
  1. servcie层单元测试的目的:
    测试中间的业务逻辑是否正确的执行,没有过度执行也没有遗漏业务逻辑;主要是对方法中的业务逻辑进行测试
    而不是需要订单真正的落库,那是mapper单元测试需要测试的内容,需要界定你单元测试的范围;也不需要去看对应商品服务接口返回的数据是否有误;
  2. 单元测试的局限性:
    一般单元测试都是由开发在本地进行测试自己的代码,而像一些请求接口并不能去调用,类似于上述例子中:需要获取用户信息或者商品信息,由于是微服务开发,我们不一定能在本地直接去调用对应的接口;那么这个应该如何测试呢?
    有两种方法,也是在总览中提到的:
    一种是桩代码:也就是在单元测试的时候,将对应的接口调用替换成一段测试专门使用的创建对应的参数进行返回,但不过这样做事有坏处的,如果在发布的时候,忘记将其改回来就会产生问题;所以更推荐的方法是第二种
    二:Mock对象;使用上述的工具Mockito;对调用的其他的微服务接口进行模拟对象;其执行原理大致是:动态生成一个被模拟对象相同类型的对象,注入到ioc容器中将其替换,然后我们在书写调用执行对应方法时的业务逻辑;

上述已经论述了为什么使用Mockito的原因与其作用了;那么为什么使用断言呢?
问题:
如何判断单元测试是否执行成功呢?
将结果进行输出System.out.println();由人工去判断是否得到自己想要的结果;那么随着业务系统规模的增大,单元测试越来越多,我们使用maven插件mvn:test一键自动化执行所有的单元测试,在一个一个去进行比较单元测试是否成功是一件很花费时间的事情;
解决:使用断言
断言的作用:比较方法得出的结果与我们预期的结果是否一致,减少用人眼去校对的过程;也就是assertJ的作用;

但具体的对应的方法,本文就不涉及了;

那么业务逻辑层的单元测试还有什么需要注意的?
注意单元测试的覆盖率;由于业务逻辑层的代码相较于其他层是较多的(不同的公司具有不同的规范,业务的复杂度也是不一样的,划分的层次也是不一样的);更需要注重测试的用例是否覆盖了大部分的代码,每一种业务场景是否都有覆盖

最后在给出一个Service层单元测试的例子吧:(基于SpringBoot)

import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;
/**
注解的意义在于Test测试类要使用注入的类,比如@Autowired注入的类,
有了@RunWith(SpringRunner.class)这些类才能实例化到spring容器中,自动注入才能生效,
**/
@RunWith(SpringRunner.class)
/**
这个是spring boot提供的,会一直找到一个Application类,只要包含了@SpringBootApplication的就算,然后会先启动这个类,来给单元测试提供环境
检索顺序是从当前包开始,逐级向上查找被@SpringBootApplication或@SpringBootConfiguration注解的类。
**/
@SpringBootTest
public class EmployeeServiceTest {
    
    @Autowired
    private EmployeeService employeeService;
    
    // 这里是基于Mockito模拟出来了一个EmployeeMapper
    // 这里定义的@MockBean,会模拟生成一个EmployeeMapper,放入spring容器中去,然后被EmployeeService给引用到;里面的方法的行为就由我们在单元测试中进行制定
    @MockBean
    private EmployeeMapper employeeMapper;
        
    @Test
    public void testService() {
        int employeeId = 1;
        Employee employee = new Employee();
        employee.setId(1);
        employee.setName("张三");
        employee.setAge(20);
        // 对employeeMapper的findById方法进行模拟
        // 无论给这个方法传入什么参数
        // 都会返回一个Employee对象,这个是我们预先定义好的employee对象
        given(this.employeeMapper.findById(anyLong()))
                .willReturn(employee);    
        Employee resultEmployee = employeeService.findById(employeeId);    
        //断言:判断是否得出我们想要的结果 
        assertEquals(employee, resultEmployee);
    }
    
}

由此Junit框架去搭建单元测试环境;Mockito去进行模拟接口对象实现方法直接的隔离,让我们单元测试的目标更加单一;assert断言判断结果是否正确,让测试的结果一目了然;

这个例子的测试显得有点多余了,因为这是一个简单的crud吧,如果类似于上面的创建订单的单元测试,其构造对象,mock接口都是十分繁琐的吧。单元测试的作用:也不仅仅只是当下之功,如果单元测试对应的代码需要增加功能,或者修改部分逻辑,你只需要运行对应的单侧就可以知道,增加的代码会不会影响之前的代码了。