Springbatch单元测试Notes

Springbatch单元测试Notes

@SpringBatchTest 注解

这个注解会注册JobLauncherTestUtilsJobRepositoryTestUtils到上下文context中 因此,有两种方式 方式1:SpringBatchTest

@SpringBatchTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes:{otherClass.class})
public class SkipSampleFunctionalTests { ... }

方式2:SpringJUnit4ClassRunner

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes:{JobLauncherTestUtils.class,
                               JobRepositoryTestUtils.class,
                               otherClass.class})
public class SkipSampleFunctionalTests { ... }

Springbatch整合Mybatis-plus单元测试并回滚数据

springboot整合mybatis-plus单元测试

如果是在springboot对mybatis-plus进行单元测试,那么需要在测试类上进行以下设置:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {someClasses.class})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.AUTO_CONFIGURED)
@MybatisPlusTest
@MapperScan(basePackages = {"com.xxx.xxx.dao"})
public class xxTest{

    @Test
    public void testXXX(){
    
    }

    @TestConfiguration
    public static class MyConfiguration{
        
        @Bean("dataSource")
        @ConfigurationProperties(prefix = "data-source")
        public DataSource dataSource(){
            return new DruidDataSource();
        }

        @Bean
        public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception{
            MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
            bean.setDataSource(dataSource);
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            String mapperLocation = "classpath:mapper/**/*Mapper*.xml";
            bean.setMapperLocations(resolver.getResources(mapperLocation));
            return bean.getObject();
        }
    }
}

其中,@MybatisPlusTest注解中配置了很多注解,基本上可以一键完成springboot+mybatis的单元测试,并且可以使用数据库数据,并完成数据回滚。

如果去掉@MybatisPlusTest,需要自行添加@Transactional注解来控制事务,使数据回滚。而且,@ConfigurationProperties注解会失效:当使用.properties配置文件时,需要在配置类上添加@TestPropertySource(location={"classpath:xxx.properties"})的注解和@EnableConfigurationProperties的注解;当使用.yml配置文件时,需要在测试类上添加@ContextConfiguration(initializers = {ConfigFileApplicationContextInitializer.class})的内容。

在springboot 2.6版本中,ConfigFileApplicationContextInitializer更新为ConfigDataApplicationContextInitializer

总而言之,@MybatisPlusTest进行了很多操作,便于整合到springboot的单元测试之中。

@MybatisPlusTest中有@AutoConfigureTestDatabase注解,但该注解默认值为AutoConfigureTestDatabase.Replace.ANY,在我的测试中,AUTO_CONFIGURED才起作用。

springboot整合springbatch单元测试

如果是在springboot对springbatch进行单元测试,那么需要在测试类上进行以下设置:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes:{JobLauncherTestUtils.class,
                               JobRepositoryTestUtils.class,
                               otherClass.class})
@EnableBatchProcessing
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.AUTO_CONFIGURED)
public class SkipSampleFunctionalTests {
    @Test
    public void testXXX(){
    
    }
    
    @TestConfiguration
    public static class MyConfiguration{
        
        @Bean("dataSource")
        @ConfigurationProperties(prefix = "data-source")
        public DataSource dataSource(){
            return new DruidDataSource();
        }
    }
}

其中,@EnableBatchProcessing注解完成了很多事,包括事务管理transactionManagerjobRepositoryjobBuilderFactorystepBuilderFactoryjobLauncherjobExplorer等等。它是依靠注入了SimpleBatchConfiguration完成的。

如果去掉@EnableBatchProcessing注解,那么需要手动配置。如下所示:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes:{JobLauncherTestUtils.class,
                               JobRepositoryTestUtils.class,
                               otherClass.class})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.AUTO_CONFIGURED)
public class SkipSampleFunctionalTests {
    @Test
    public void testXXX(){
    
    }
    
    @TestConfiguration
    public static class MyConfiguration{
        
        @Bean("dataSource")
        @ConfigurationProperties(prefix = "data-source")
        public DataSource dataSource(){
            return new DruidDataSource();
        }

        @Bean("transactionManager")
        public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }

        @Bean
        public MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean(@Qualifier("transactionManager") PlatformTransactionManager transactionManager) throws Exception {
            MapJobRepositoryFactoryBean factoryBean = new MapJobRepositoryFactoryBean();
            factoryBean.setTransactionManager(transactionManager);
            factoryBean.afterPropertiesSet();
            return factoryBean;
        }

        @Bean("jobRepository")
        public JobRepository jobRepository(@Qualifier("mapJobRepositoryFactoryBean") MapJobRepositoryFactoryBean factoryBean) throws Exception{
            return factoryBean.getObject();
        }

        @Bean
        public JobBuilderFactory jobBuilderFactory(@Qualifier("jobRepository") JobRepository jobRepository){
            return new JobBuilderFactory(jobRepository);
        }

        @Bean
        public StepBuilderFactory stepBuilderFactory(@Qualifier("transactionManager") PlatformTransactionManager transactionManager,
                                                     @Qualifier("jobRepository") JobRepository jobRepository){
            return new StepBuilderFactory(jobRepository, transactionManager);
        }

        @Bean
        public JobLauncher jobLauncher(@Qualifier("jobRepository") JobRepository jobRepository){
            SimpleJobLauncher launcher =  new SimpleJobLauncher();
            launcher.setJobRepository(jobRepository);
            return launcher;
        }

        @Bean
        public JobExplorer jobExplorer(@Qualifier("mapJobRepositoryFactoryBean") MapJobRepositoryFactoryBean factoryBean){
            return new SimpleJobExplorer(factoryBean.getJobInstanceDao(), factoryBean.getJobExecutionDao(),
                    factoryBean.getStepExecutionDao(), factoryBean.getExecutionContextDao());
        }
    }
}

并且,即使加上了@EnableBatchProcessing注解,也可以通过重写batchConfigurer自定义加载上述bean,如下所示:

/*
因为@EnableBatchProcessing引入了SimpleBatchConfiguration,
而SimpleBatchConfiguration中配置了名为transactionManager的事务管理器,
因此要将上文中定义的transactionManager重命名
*/

@Bean("myTransactionManager")
public PlatformTransactionManager myTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

@Bean
public BatchConfigurer batchConfigurer(MapJobRepositoryFactoryBean mapJobRepositoryFactory,
                                       PlatformTransactionManager myTransactionManager,
                                       JobLauncher jobLauncher,
                                       JobExplorer jobExplorer) {
    return new BatchConfigurer() {
        @Override
        public JobRepository getJobRepository() throws Exception {
            return mapJobRepositoryFactory.getObject();
        }

        @Override
        public PlatformTransactionManager getTransactionManager() throws Exception {
            return transactionManager;
        }

        @Override
        public JobLauncher getJobLauncher() throws Exception {
            return jobLauncher;
        }

        @Override
        public JobExplorer getJobExplorer() throws Exception {
            return jobExplorer;
        }
    };
}

上述代码连接了数据库,但实际中并不推荐。因为单用springbatch的注解进行单元测试,不能数据回滚,在网上并没有找到相关的办法,因此推荐连接内存数据库使用。如果批处理任务是读文件写入数据库或者从数据库写出到文件,那么这种单元测试足够了。但是,如果批处理任务涉及多张数据表查询操作,那么构造数据可能会是一个大问题。

springboot+springbatch+mybatis-plus整合进行单元测试

先抛出问题,同时使用@EnableBatchProcessing@MybatisPlusTest会有事务冲突。 解决办法,很简单,但从两个很方便的注解直接入手查资料,着实很废时间,这里不卖关子,直接给出答案。

@Bean
public MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean(@Qualifier("transactionManager") PlatformTransactionManager transactionManager) throws Exception {
    MapJobRepositoryFactoryBean factoryBean = new MapJobRepositoryFactoryBean();
    factoryBean.setTransactionManager(transactionManager);
    factoryBean.setValidateTransactionState(false); // 在这里
    factoryBean.afterPropertiesSet();
    return factoryBean;
}

这一行,就行了。setValidateTransactionState(false)的作用是禁用在使用MapJobRepositoryFactoryBean时对事务状态的验证。这意味着在使用该工厂创建的 JobRepository 时,不会检查当前事务状态是否为活动状态。之后,是否使用@EnableBatchProcessing@MybatisPlusTest注解,都无所谓了。这样也可以发现,可以拿到很多个版本的单元测试代码。