Spring Junit 服务层单元测试实践


一、背景

最近在做公司的计费系统模块重构的项目,涉及到单元测试,关于单元测试遇到了事务提交不回滚的问题,另外也做了单元测试代码的重构,有了一些体验。在此记录一下。

二、事务提交与回滚

2.0 背景

因为老版本计费系统与业务模块耦合,同时计费场景比较多,因此业务上决定进行解耦和拆分。在进行代码重构之前已经做了业务分析,对不同的业务场景做对应的计费类型分类和子场景梳理。因此从模块上来说,可以使用策略模式从不同计费类型上去解决,另外子场景则可以通过对应的Spring Service服务类具体应对。在基本代码重构完成之后就需要考虑到测试问题。

2.1 Spring Junit事务问题

由于当前项目并没有test相关的测试代码,所以我在问了相关同事之后增加了测试代码内容,遇到的第一个问题是如何初始化项目,也就是启动Spring 容器。因为项目启动依赖Nacos环境,这里需要显示的设置一下变量标识,所以定义了一个基类:Spring Junit 服务层单元测试实践通过设置系统属性的方式,让Spring 容器拿到对应的环境标识进而匹配到Nacos对应环境的配置顺利启动项目。

第二步,将applicationContext.xml复制一份到test/resources目录下,新建一个Spring测试类测试框架启动情况:

@RunWith(SpringJUnit4ClassRunner.class)//加载spring容器
@ContextConfiguration("classpath:applicationContext_test.xml")//加载配置文件
@Slf4j
@Transactional
public class SpringTest extends BaseTest {
}

下面添加测试类:

    /**
     * bill insert transaction test
     * 设置Rollback注解,事务不会提交
     */
    @Test
    @Rollback
    public  void testTransaction(){
        Bill bill = new Bill(BillType.XXX);
        bill.setStatus(0);
        bill.setSubType(xx);
        bill.setOrgId(1L);
        bill.setTeamId(0L);
        billMapper.insert(bill);
        Bill persistBill = billMapper.findByIdAndOrgId(bill.getId(),bill.getOrgId());
        Assert.assertEquals(bill.getId(), persistBill.getId());
    }

注意上面的代码其实没有回滚,当前项目的Spring版本是4.x系列的,但是在单元测试方面不会太老,从网上查了下,跟被调用的类上是否有事务注解也有关系,但是实际上跟上面的测试代码是有冲突的,因为单元测试类本身已经声明事务了。 在网上大部分的解决方案场景都不太符合的时候发现这个项目其实是个多数据源项目(Mybatis+Hibernate连接多个数据库),虽然单元测试日志中显示回滚了,但是由于数据源方面的问题,可能没有真正回滚,于是增加一个注解指定数据源:

@RunWith(SpringJUnit4ClassRunner.class)//加载spring容器
@ContextConfiguration("classpath:applicationContext_test.xml")//加载配置文件
@Slf4j
@Transactional
//防止事务回滚失效,通过注解显示指定事务管理器
@TransactionConfiguration(transactionManager ="mybatisTransactionManager")
public class SpringCTITest extends BaseTest {
.....
}

IDEA中显示可以看到@TransactionConfiguration注解其实已经过期了,但是实际上我添加完这个注解之后事务确实回滚了。所以从这里应该可以看出,如果数据源是唯一的,那么在单元测试的时候可能就不需要加这个注解了,那在事务上的数据库连接是唯一的应该就可以回滚。

三、单元测试代码重构

3.1 基于模块构建单元测试基类

@RunWith(SpringJUnit4ClassRunner.class)//加载spring容器
@ContextConfiguration("classpath:applicationContext_test.xml")//加载配置文件
@Slf4j
@Transactional
@TransactionConfiguration(transactionManager ="mybatisTransactionManager", defaultRollback = true)
public class AbstractChargeTest extends BaseTest {
    @Autowired
    public BillContextFactory billContextFactory;

    @Autowired
    public  BillStrategyFactory billStrategyFactory;

    public  BillEvent message;

  /**
     * 通过构造方法初始化通用的消息字段
     */
    public AbstractChargeTest(){
        this.message = new BillEvent();
        this.message.setOrgId(0L);
        this.message.setBillOrgVersion(1);
        this.message.setDataSource("xx");
        this.message.setKey(UUID.randomUUID().toString());
    }

  /**
     * 初始化billLogEvent
     * @param billExtData
     * @param billType
     * @param subBillType
     * @param billSenseType
     */
    public void initBillLogEvent(BaseBillExtData billExtData, String billType, Integer subBillType, String billSenseType){
        this.message.setBaseBillExtData(billExtData);
        this.message.setBillType(billType);
        this.message.setBillSubType(subBillType);
        this.message.setBillSenseType(billSenseType);
    }

    /**
     * 执行策略
     */
    public void exeStrategy(){
        //获取计费策略实现类
        BillCalculateStrategyService billStrategyService = billStrategyFactory.getBillStrategyService(
                message.getBillSenseType());

        //构建计费上下文
        BillContext billContextBO = billContextFactory.buildBillContextFromEvent(message);

        //进入计费策略实现
        billStrategyService.billCalculate(billContextBO);
    }

}

这里定义一个计费模块的抽象测试基类,在里面定义一个类似于业务执行模板代码的基本实现。基于此可以提供一种单元测试代码的思路,就是在关键的地方将代码抽取出来当作单元测试的核心方法,一方面辅助测试,另外一方面可以保持单测代码的可维护性等。

3.2 基于业务流程构建测试代码执行流程

这里的单测是测试计费消息接收到消息后具体进行业务逻辑处理的地方,也就是说通过上面的测试代码编写,将不容易变化的地方分别使用构造方法和核心代码段重新组织起来,将容易变化的地方下沉到子类测试代码中。

/**
 * @Author fanchunshuai
 * @Description 计费消息消费测试
 * @Version 1.0
 */

public class BillEventConsumeTest extends AbstractChargeTest {

    /**
     * 测试agent-charge场景消费
     */
    @Test
    @Rollback
    public void testConsumeAgentCharge(){
        AgentBillExtData agentBillBillExtData = new AgentBillExtData();
        agentBillBillExtData.setChildBillSenseType(ChildSenseTypeEnum.CHARGE.getChildSenseType());
        agentBillBillExtData.setDay(3);
        agentBillBillExtData.setMonth(true);
       
        super.initBillLogEvent(agentBillBillExtData, BillType.AGENT.name(), null,
                BillSenseTypeEnum.AGENT.getBillSenseType());
   
        super.exeStrategy();
    }

}

3.3 总结

本文从单元测试集成到项目的过程中阐述其中出现的一些问题,比如事务不回滚的问题,容器启动的问题,在书写单元测试代码过程中如何更好的组织单测代码的问题。从单元测试代码的书写过程中可以感受到实现业务代码之后再写单元测试类似于写了两遍,但是不需要再花更多精力去想业务逻辑实现了,而是单纯的单测。

原文始发于微信公众号(神帅的架构实战):Spring Junit 服务层单元测试实践

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/241359.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!