单元测试
- 对最小单元(函数、方法、类)程序进行逻辑覆盖,并且获得正确的预期值
问题
- 逐个方法/接口进行测试,方法内部依赖的其他方法/远程接口/IO等都进行mock,这些mock的东西单独再进行测试
- 逐个方法/接口进行测试,仅仅mock 远程接口,其他的(数据库IO/中间件【mq、redis、apollo等】)不mock
总结:暂不考虑严格唯独的单元测试,采用第二个方法
spring 环境下的单元测试
<build>
<finalName>upex-act-promotion-job</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<skipTests>false</skipTests>
</configuration>
</plugin>
</plugins>
</build>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-mockserver</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.11.2</version>
<scope>test</scope>
</dependency>
@Configuration
@EnableApolloConfig("application")
public class ApolloConfig {
@ClassRule
public static EmbeddedApollo embeddedApollo = new EmbeddedApollo();
}
自动加载classPath下的mockdata-application.properties
@Configuration
@EnableApolloConfig("application")
@Slf4j
public class H2Config {
@Primary
@Bean("dataSource")
public DataSource dataSource() {
Config appConfig = ConfigService.getAppConfig();
DataSource ds = DataSourceBuilder.create()
.driverClassName(appConfig.getProperty("spring.datasource.upex.driverClassName","org.h2.Driver"))
.url(appConfig.getProperty("spring.datasource.upex.url","jdbc:h2:mem:upex_act;MODE=MYSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;"))
.username(appConfig.getProperty("spring.datasource.upex.username","pdai"))
.password(appConfig.getProperty("spring.datasource.upex.password","pdai"))
.build();
String property = appConfig.getProperty("unit.sql", "init.sql");
String[] sqls = property.split(",");
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
for (String sql:sqls){
databasePopulator.addScript(new ClassPathResource(sql));
}
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
dataSourceInitializer.setDataSource(ds);
dataSourceInitializer.setDatabasePopulator(databasePopulator);
dataSourceInitializer.setEnabled(true);
dataSourceInitializer.afterPropertiesSet();
return ds;
}
@Bean
@Primary
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") final DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:/mybatis/mapper/*.xml");
sessionFactory.setMapperLocations(resources);
SqlSessionFactory sqlSessionFactory = sessionFactory.getObject();
assert sqlSessionFactory != null;
org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
configuration.setMapUnderscoreToCamelCase(true);
return sqlSessionFactory;
}
@Bean
@Primary
public PlatformTransactionManager transactionManager(@Qualifier("dataSource") final DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
@PreDestroy
public void preDestroy(){
try (Connection connection = dataSource().getConnection(); Statement dropTableStatement = connection.createStatement()) {
String schema = connection.getSchema();
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(null, schema, null, new String[]{"TABLE"});
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
String dropTableSql = "drop table " + tableName + " cascade";
dropTableStatement.execute(dropTableSql);
}
connection.commit();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
初始化数据库
@Configuration
@Slf4j
public class RedisConfig{
private RedisServer redisServer;
@PostConstruct
public void postConstruct() {
log.info("redis内存测试数据库starting......");
redisServer = RedisServer.builder()
.port(6379)
.setting("maxmemory 128M")
.build();
redisServer.start();
log.info("redis内存测试数据库started !!!!!!");
}
@PreDestroy
public void preDestroy() {
redisServer.stop();
log.info("redis内存测试数据库stop !!!!!!");
}
}
**
* 基础测试类
* @author fly.cheng
**/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringJUnitConfig(classes = {
ApolloConfig.class,
RedisConfig.class,
H2Config.class,
TransactionHelper.class,
RedissonAutoConfig.class,
ActivityRedisService.class,
ApTransactionServiceImpl.class
})
@EnableApolloConfig("application")
@MapperScan(basePackages = "com.xxx.service.**.mapper")
@WebAppConfiguration
public abstract class BaseTest {
@ClassRule
public static EmbeddedApollo embeddedApollo = new EmbeddedApollo();
@Resource
RedisConfig redisConfig;
@Resource
H2Config h2Config;
@After
public void clear(){
redisConfig.preDestroy();
h2Config.preDestroy();
}
}
1、扫描持久层
2、注入 redis、h2、apollo bean
3、清理资源
注解
- @MockBean spring boot starter test
- mockStatic(xxx.class) mockito
- when(xx.xx())thenReturn() mockito
- @Test junit
- @DisplayName junit
- @RunWith(SpringJUnit4ClassRunner.class) junit
- @SpringJUnitConfig(classes = {
xxx.class
}) spring junit
- Assert.assertXxxxxx junit