spock

1、dbunit 字段不能为空的问题

dbUnit / Bugs / #363 Regression: cannot insert an empty string
解决办法

  1. 为啥要这样做?在实际的数据表中有text类型的字段,值是可以为空字符串或者null的

2、spock commit 后格式错乱的问题,留一行即可解决

image.png

4、按照目前的idea plugin 开发一套适合spock格式的dbunit插件

image.png
:::tips
skill (skill_id: “4”,skill_name: “家居安装维修”,parent_id: “0”,sort: “1”,level: “1”,create_time: “1614061455573”,update_time: “1644839217688”)
:::
dbunit-extractor.jar.zip
使用的时候把zip去掉,再导入到idea中

5、mybatis 一级缓存导致的脏读:

场景:在使用spock时发现的一个问题

public class BusinessService{
    public void setSelfOwned(BusinessSelfOwnedDTO dto) {
        Business business = businessMapper.selectById(dto.getBusinessId());
        ApiException.throwError(null == business, ApiErrorCode.BUSINESS_NOT_FOUND);
        business.setIsSelfOwned(dto.getIsSelfOwned());
        //        businessMapper.updateById(business);
    }
}


   
    @Unroll
    def "test setSelfOwned"() {
        given:
        def ownedDTO = new BusinessSelfOwnedDTO(businessId: businessId, isSelfOwned: isSelfOwned)
        when:
        businessService.setSelfOwned(ownedDTO)
        then:
        def business = businessService.businessMapper.selectById(businessId)
        business.getIsSelfOwned() == isSelfOwned
        println(business)
        where:
        businessId | isSelfOwned
        1 		   | 1
        1          | 0
        1          | 0
    }

发现无论怎么都是 pass

解决方案:

配置 localCacheScope

public class DataSourceHolder {
    //    public static SqlSessionFactory builder = new MybatisSqlSessionFactoryBuilder().build(DataSourceHolder.class.getClassLoader().getResourceAsStream("mybatis-config.xml"));
    public static SqlSessionFactory builder;
    public static Configuration configuration;

    public static DataSource dataSource = SpecUtils.inMemoryDataSource();


    static {
        //这种方式主要是为了兼容dbUnit
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        try {
            factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
            factory.setDataSource(SpecUtils.inMemoryDataSource());
            builder = factory.getObject();

            MybatisConfiguration configurationT = (MybatisConfiguration) builder.getConfiguration();
            //解决一级缓存导致的脏读
            //场景: 下面方法中没有update,在重复单元测试时发现查询出来的数据竟然是缓存的数据
            /*public void setSelfOwned(BusinessSelfOwnedDTO dto) {
                    Business business = businessMapper.selectById(dto.getBusinessId());
                    ApiException.throwError(null == business, ApiErrorCode.BUSINESS_NOT_FOUND);
                    business.setIsSelfOwned(dto.getIsSelfOwned());
                    //businessMapper.updateById(business);
                }
             */
            configurationT.setLocalCacheScope(LocalCacheScope.STATEMENT);
            GlobalConfig globalConfig = configurationT.getGlobalConfig();
            globalConfig.setMetaObjectHandler(new MybatisPlusHandler());
            configuration = builder.getConfiguration();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        //这里是原mybatis的方式
        //       SqlSessionFactory builder = new SqlSessionFactoryBuilder().build(SkillServiceImplSpec.class.getClassLoader().getResourceAsStream("mybatisTestConfiguration/mybatis-config.xml"));
        //这里是mybatisPlus 封装后的
        // 作用一:处理entity类和mysql中字段不一致的问题,java中是驼峰式,java是下划线
        // 作用二:处理mapper 继承 baseMapper 原mapper没有baseMapper中封装的statement
        // 缺点一:因为是非spring模式,在启动后所有的插件都会失效
        //解决非spring模式下导致metaObjectHandler缺失的问题,主要就是新增或者更新时,自动填充字段的问题
        /*MybatisConfiguration configurationT = (MybatisConfiguration) DataSourceHolder.builder.getConfiguration();
        GlobalConfig globalConfig = configurationT.getGlobalConfig();
        globalConfig.setMetaObjectHandler(new MybatisPlusHandler());
        configuration = DataSourceHolder.builder.getConfiguration();*/

    }
    public static void initTable() {
        String sql = FileUtil.readString("classpath:test/db/init_table.sql", StandardCharsets.UTF_8);
        new Sql(dataSource).execute(sql);
    }
}

MyBatis一级缓存_xinyuan_java的博客-CSDN博客_mybatis localcachescope
一二级缓存的关系
spock

6、spock单元测试框架的使用

@SpringBootTest
@ContextConfiguration(classes = UserServiceImpl.class)
@WebAppConfiguration
class UserInfoSpec extends Specification {
        @Autowired
        UserServiceImpl userService
        @SpringBean
        UserMapper userMapper = Mock()
        def setup() {}
        @Unroll
        def "testSelectUserById"() {
            given:
            def user = new User(userId: userId, userName: userName, passWord: passWord);
            userMapper.seleteUserById(userId) >> user
            expect:
                userService.selectUserById(userId) == user
            where:
                userId   | userName | passWord
            1234567L | xiaoming | 123456
            12309756 | xiaohong | 4321134
        }
    }



作者:hancaicai
链接:https://ld246.com/article/1573907697146
        来源:链滴
协议:CC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/

包名规范路径
参考:
定位

@Unroll("非法用户名:#exampleUsername")
    def "用户名需是2-16位数字、字母和中文混合"() {
        when: "初始化用户名"
        username = new Username(exampleUsername)
        then: "不符合规则时抛出异常"
        thrown(IllegalArgumentException)
        where:
        exampleUsername << [
                "1",
                "12345678901234561",
                "杜",
                "国破山河在凑足十七个个字才行啊是不",
                "有符号 。 所以不行",
                "英文.符号所以不行",
                "有空格 不行",
                "",
        ]

    }

项目参考:
GitHub - imcamilo/kotlin-spring-project: Kotlin + MyBatis + Spock + Spring Boot + Docker

覆盖率
IDEA集成jacoco - 紫陌花间客 - 博客园

关于代码覆盖率,可以使用idea自带的coverage,勾选Tracing,可以看到分支是否完全覆盖。

Spock单元测试框架介绍以及在美团优选的实践
Spock系列 - 老K的Java博客
github上的spock实战项目
GitHub - lucas-myx/spock_example: Spock是国外一款功能强大的测试框架,但是官方的文档和代码示例不太适合我们实际的工程项目,无法解决我们项目中的复杂业务场景,需要找到一套适合自己项目的成熟解决方案,所以觉得有必要把我们项目中使用Spock的经验分享出来,帮助大家解决实际问题或带来一些启发,如果你在使用过程中遇到问题可以在公众号[Java老K]或我的博客 //javakk.com 交流沟通

 @Unroll
    def "test login with "(){
        when:
        userService.login(name, passwd)

        then:
        userDao.findByName("k") >> null
        userDao.findByName("k1") >> new User("k1", 12, "p")

        then:
        def e = thrown(RuntimeException)
        e.message == errMsg

        where:
        name        |   passwd  |   errMsg
        "k"         |   "k"     |   "${name}不存在"
        "k1"        |   "p1"     |   "${name}密码输入错误"

    }
使用mybatisplus参考文章:

MyBatis Plus_Kramer_149的博客-CSDN博客
记载一下成功后的案例:


import com.alibaba.testable.core.annotation.MockDiagnose
import com.alibaba.testable.core.model.LogLevel
import com.baomidou.mybatisplus.core.MybatisConfiguration
import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder
import org.apache.ibatis.session.SqlSessionFactory
import spock.lang.Specification
import spock.lang.Unroll

/*// 注意这里要使用 unittest 的配置文件了,里面使用H2数据库
@ActiveProfiles("unittest")
// 关于@Rollback(false)注解:正常情况下,SpringBoot的测试用例每执行完一个就会回滚一次,
// 比如说再执行完create entity test之后会立马将刚刚insert进去的数据清除,之后执行“get entity test”的时候应该是测试用例不通过的。
// 加了这个注解后,就可以指定某个测试方法,或者某个测试类的执行结果不进行回滚。
@Rollback
@MybatisPlusTest*/

//@ActiveProfiles("unittest")
//@MybatisPlusTest
class SkillServiceImplSpec extends Specification {

    private SkillServiceImpl skillService;


    void setup() {
        //这里是原mybatis的方式
        //       SqlSessionFactory builder = new SqlSessionFactoryBuilder().build(SkillServiceImplSpec.class.getClassLoader().getResourceAsStream("mybatisTestConfiguration/SkillMapperTestConfiguration.xml"));
        //这里是mybatisPlus 封装后的
        // 作用一:处理entity类和mysql中字段不一致的问题,java中是驼峰式,java是下划线
        // 作用二:处理mapper 继承 baseMapper 原mapper没有baseMapper中封装的statement
        // 缺点一:因为是非spring模式,在启动后所有的插件都会失效
        SqlSessionFactory builder = new MybatisSqlSessionFactoryBuilder().build(SkillServiceImplSpec.class.getClassLoader().getResourceAsStream("mybatisTestConfiguration/SkillMapperTestConfiguration.xml"));
        //解决非spring模式下导致metaObjectHandler缺失的问题,主要就是新增或者更新时,自动填充字段的问题
        def configuration = (MybatisConfiguration) builder.getConfiguration()
        configuration.getGlobalConfig().metaObjectHandler = new MybatisPlusHandler();
//        //you can use builder.openSession(false) to not commit to database

        SkillMapper mapper = builder.getConfiguration().getMapper(SkillMapper.class, builder.openSession(true));
        skillService = new SkillServiceImpl();
        skillService.baseMapper = mapper
        skillService.skillMapper = mapper
        skillService.skillConverter = new SkillConverterImpl();
        skillService.groupServiceMenuMapper = Stub(GroupServiceMenuMapper)
        skillService.workerSkillRelationMapper = Stub(WorkerSkillRelationMapper)
    }

    @MockDiagnose(LogLevel.VERBOSE)
    public static class Mock {

    }

    @Unroll
    def "test save"() {
        given: "修改3级"
        def dto = new SkillSaveDto(skillId: 3, skillName: "新三级名称", parentId: 2, level: 3)
        when: "不存在服务菜单数据,不存在服务数据"
        skillService.save(dto)
        then:
        println "执行成功"

    }


}

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Mybatis config sample -->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>


  <environments default = "default">
    <environment id="default">
      <transactionManager type="JDBC"/>
      <dataSource type="UNPOOLED">
        <property name = "driver" value = "com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://"/>
        <property name="username" value=""/>
        <property name="password" value=""/>
      </dataSource>
    </environment>
  </environments>



  <mappers>
    <mapper resource="mapper/SkillMapper.xml"/>
  </mappers>
</configuration>

dbunit 参考:
https://github.com/janbols/spock-dbunit
image.png
驼峰转下划线:
在线工具-驼峰转下划线,下划线转驼峰-BeJSON.com
有赞单元测试实践

  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值