1. 测试前的基建数据库表、实体类与Mapper文件的准备逻辑先说个实在话很多人在添加功能这一环翻车根本不是Mapper写错而是测试前的准备工作没做到位。我见过太多新手把insert标签写完后直接跑测试结果一堆报错最后排查半天发现是数据库表字段和实体类属性对不上或者主键策略没想清楚。所以这一篇虽然标题叫测试添加功能但真正动手写测试代码之前有几件事必须踏实搞定。第六篇做到这里前面五篇你应该已经把开发环境、Maven项目结构、MyBatis配置文件都搭好了那咱们直接从测试角度反推需要哪些前置条件。1.1 数据库表中的字段与Java实体类属性的映射关系确认添加功能最核心的任务是把Java对象中的数据通过MyBatis框架写入到数据库表的对应字段中。这听起来简单但映射关系一旦错位运行期不会立刻炸而是在数据落库后你才发现这个字段怎么是空的或者那个字段的值怎么跑到另一列去了。建议的做法是拿一张最简单的表来做测试比如用户表t_userCREATE TABLE t_user ( id INT(11) NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100) DEFAULT NULL, create_time DATETIME DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;对应的实体类public class User { private Integer id; private String username; private String email; private Date createTime; // getter / setter 省略 }注意看数据库字段create_time对应实体类属性createTime也就是下划线转驼峰。MyBatis默认是不开启驼峰映射的所以你需要两个选择在SQL语句里为每个字段显式指定别名例如select create_time as createTime from t_user——这是查询时的处理方式。对于添加功能而言insert语句里用的是#{}占位符占位符内部填的是实体类属性名而不是数据库字段名。所以写#{createTime}时MyBatis会去实体类中找getCreateTime()方法。这一点看似基础但很多人卡在这里。尤其是从别的框架比如MyBatis-Plus转过来的人习惯了默认驼峰映射到了原生MyBatis里发现全是坑。我个人的建议是不要靠别名凑合直接在MyBatis全局配置中开启驼峰映射这样查询和插入都省心。配置如下settings setting namemapUnderscoreToCamelCase valuetrue/ /settings但注意驼峰映射只影响resultType自动映射和resultMap中未显式指定的字段映射。在insert语句里#{}中写的依然是实体类属性名跟驼峰映射开不开没有关系。1.2 Mapper接口与XML文件的对应关系检查清单这是第六篇前面几篇应该已经把Mapper接口和XML文件都创建好了。但在写测试代码之前我建议对照下面的清单逐项核对一遍缺一不可Mapper接口的包路径、接口名是否与XML文件的namespace完全一致。XML文件中insert标签的id是否与接口中方法名一致。接口方法的返回值和参数类型是否与insert标签匹配。XML文件是否被注册到MyBatis配置中要么通过mappers标签逐个注册要么配置了mapperLocations通配路径。Mapper接口上是否标注了Mapper注解或者在Spring配置中配置了MapperScan。这些内容在老手眼里是下意识检查的东西但对新手来说任何一个点遗漏测试跑起来都是灾难性的报错。比如最经典的报错Invalid bound statement (not found): com.example.mapper.UserMapper.insert这个报错90%的原因就是namespace写错了或者接口方法名和XML的id不一致。记住它以后你会经常见到。2. 添加功能的核心实现从单条插入到主键回填的取舍测试之前你得先明确添加功能到底要测哪些能力。这里我把添加功能拆成几个层次每一层的测试重点都不一样最基础的单条记录插入参数用实体对象传递。进阶的插入后获取自增主键值主键回填。更复杂的动态SQL插入比如有if判断字段是否为空。批量插入。本篇测试的重点是前两个因为它们是整个添加功能的地基。动态SQL我建议等动态SQL那篇文章再专项测试。2.1 最简单的insert写法以及#{}与${}的区别先看基础的单条插入SQLinsert idinsertUser parameterTypecom.example.entity.User INSERT INTO t_user (username, email, create_time) VALUES (#{username}, #{email}, #{createTime}) /insert对应的Mapper接口方法int insertUser(User user);请注意这里的参数只有一个且是自定义类型所以#{}里直接写属性名即可。如果方法有多个参数就需要加上Param(xxx)注解这是后面文章的内容。这里我必须强调一个所有教程都应该反复念叨的点永远用#{}不要用${}。#{}会被MyBatis解析成JDBC的预编译占位符?参数通过PreparedStatement传入天然防SQL注入而${}是做字符串拼接直接把值拼进SQL里看起来方便实则后患无穷。举一个容易引发事故的场景如果你用${}直接拼接用户名而且这个值来自前端输入那么用户在输入框里填 OR 11 --你的SQL就变成了SELECT * FROM t_user WHERE username OR 11 --这等于把整张表的数据都查出来了。这是安全红线问题测试环节就必须拦截。2.2 useGeneratedKeys主键回填为什么比selectKey更推荐插入用户后业务里往往马上需要这个新用户的ID——比如下一个操作要插入这个用户的订单记录外键需要用户ID。数据库的自增主键是在INSERT执行时由MySQL生成的但Java对象在INSERT执行前并不知道生成的ID是多少。两种方案解决这个问题方案一useGeneratedKeyskeyPropertyinsert idinsertUser parameterTypecom.example.entity.User useGeneratedKeystrue keyPropertyid INSERT INTO t_user (username, email, create_time) VALUES (#{username}, #{email}, #{createTime}) /insert方案二selectKey标签insert idinsertUser parameterTypecom.example.entity.User selectKey keyPropertyid orderAFTER resultTypejava.lang.Integer SELECT LAST_INSERT_ID() /selectKey INSERT INTO t_user (username, email, create_time) VALUES (#{username}, #{email}, #{createTime}) /insert两种方案区别在于底层机制。useGeneratedKeys使用的是JDBC的Statement.getGeneratedKeys()方法直接从数据库驱动层拿回自增主键而selectKey是在INSERT执行后再执行一次SELECT LAST_INSERT_ID()查询来获得主键。我的实战感受是尽量使用useGeneratedKeys。原因有几点少一次SQL执行性能略优。写法更简洁逻辑更清晰。在大部分主流数据库MySQL、PostgreSQL等上支持良好。另外注意一个关键点keyProperty的值是实体类属性名不是数据库字段名。这里如果写id但实体类叫userId必然回填失败而且MyBatis不会报错你拿到的 id 还是null。这种静默失败最坑人测试时一定要主动打印回填后的实体对象来做验证。3. 测试代码的三种写法从启动类Debug到Spring Boot Test测试添加功能我见过三种主流的写法。按从简单到正规排序分别是写一个普通的main方法在启动类中加载MyBatis配置手动获取Mapper执行添加。用JUnit写单元测试测试类里手动构建SqlSessionFactory。如果项目整合了Spring Boot直接用SpringBootTest注解写集成测试。三种写法各有适用场景我逐个说说。3.1 第一种直接写main方法用原生SqlSession验证这种方式不依赖Spring纯粹是MyBatis的原生流程。适合刚搭好框架、还没整合Spring的阶段。代码如下public class InsertTest { public static void main(String[] args) throws IOException { // 1. 读取核心配置文件 String resource mybatis-config.xml; InputStream inputStream Resources.getResourceAsStream(resource); // 2. 构建SqlSessionFactory SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream); // 3. 打开SqlSession try (SqlSession session sqlSessionFactory.openSession(true)) { // 4. 获取Mapper接口的代理对象 UserMapper mapper session.getMapper(UserMapper.class); // 5. 构造实体对象 User user new User(); user.setUsername(test_user); user.setEmail(testexample.com); user.setCreateTime(new Date()); // 6. 执行插入 int rows mapper.insertUser(user); // 7. 验证 System.out.println(影响行数 rows); System.out.println(回填后的用户ID user.getId()); } } }这里有两个细节要提醒细节一openSession(true)参数的含义。true表示自动提交事务。如果你写openSession()不带参数默认是手动提交事务也就是说insertUser执行完如果不调用session.commit()数据不会真正写入数据库。这个坑特别常见——测试跑完没报错结果一看数据库里没数据十有八九就是忘了commit。细节二try-with-resources写法。SqlSession实现了Closeable接口用try-with-resources可以在代码块结束后自动关闭避免连接泄漏。手动session.close()当然也可以但一旦中间代码抛异常close就可能执行不到连接就泄漏了。3.2 第二种JUnit 4单元测试构建可重复运行的测试用例main方法的问题在于一次只能跑一次多次测试得反复改代码。换成JUnit之后测试用例可以重复执行还能断言结果是否符合预期。public class UserMapperTest { private SqlSessionFactory sqlSessionFactory; Before public void setUp() throws IOException { String resource mybatis-config.xml; InputStream inputStream Resources.getResourceAsStream(resource); sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream); } Test public void testInsertUser() { try (SqlSession session sqlSessionFactory.openSession(true)) { UserMapper mapper session.getMapper(UserMapper.class); User user new User(); user.setUsername(junit_user); user.setEmail(junitexample.com); user.setCreateTime(new Date()); int rows mapper.insertUser(user); Assert.assertEquals(1, rows); Assert.assertNotNull(user.getId()); // 验证主键已回填 System.out.println(插入成功ID user.getId()); } } }JUnit的Before会在每个测试方法执行前都重新构建SqlSessionFactory这样做的好处是测试间隔离互不干扰。缺点是每个测试都重复加载配置如果测试量大运行时间会变长。但对于学习阶段的添加功能测试这个成本完全可以接受。3.3 第三种Spring Boot整合法最接近生产真实环境如果你前面搭建框架时就整合了Spring Boot——从相关热词里我注意到不少人在搜springboot框架springboot mybatis结合mvc框架设计——那么测试代码可以更简洁SpringBootTest RunWith(SpringRunner.class) public class UserMapperSpringBootTest { Autowired private UserMapper userMapper; Test Transactional // 测试事务回滚避免脏数据 public void testInsertWithSpringBootTest() { User user new User(); user.setUsername(springboot_user); user.setEmail(springexample.com); user.setCreateTime(new Date()); int rows userMapper.insertUser(user); Assert.assertEquals(1, rows); Assert.assertNotNull(user.getId()); // 手动查一次数据库验证数据真的进去了 User inserted userMapper.selectUserById(user.getId()); Assert.assertNotNull(inserted); Assert.assertEquals(springboot_user, inserted.getUsername()); } }注意Transactional注解。在单元测试中加上它测试结束后事务会自动回滚数据不会残留在数据库中。这样反复跑测试也不会积累垃圾数据。如果你用了Transactional上面openSession(true)的自动提交概念就不适用了因为事务由Spring统一管理。这里我多说一句测试添加功能最佳的验证方式不是只看影响行数而是插入后立刻再查一次把记录捞出来比对字段值。只看影响行数等于只验证了SQL没报错但字段是否正确落库必须通过查询来验证。4. 测试中添加功能最容易踩的五个坑以及完整排查链路下面这部分是我最想写的。添加功能的测试代码逻辑不复杂但实际跑起来报错五花八门。我把这几年见过的高频坑整理出来并带大家走一遍真正的排查思路而不是直接给答案。4.1 坑一中文乱码**表现**插入的中文数据在数据库中是乱码或者查询出来是问号。排查链路先看数据库表字符集。SHOW CREATE TABLE t_user;如果表的默认字符集是utf8而不是utf8mb4表情符号和生僻字会存不进去。建议直接用utf8mb4。再看数据库连接URL。Jdbc URL中必须有characterEncodingutf8。注意这里的值写utf8即可MySQL驱动底层会处理成utf8mb4。检查IDE控制台编码。如果你发现数据库里的中文是对的但控制台打印乱码那是IDE控制台编码问题不是框架问题。把IDE的file.encoding设为UTF-8。检查XML文件本身编码。MyBatis的XML映射文件如果保存时不是UTF-8编码SQL里的中文注释或者动态SQL片段就会出现乱码。**最容易被忽略的**使用Maven时如果XML文件放在src/main/resources下几乎没有编码问题但如果放在src/main/java目录下有人图方便会把Mapper XML和接口放一起Maven编译时可能不以UTF-8处理。所以我从第一篇文章就强调XML文件乖乖放resources目录。4.2 坑二插入影响行数为1但数据库查不到数据**表现**控制台打印影响行数1代码也不报错但打开数据库客户端查询表里空空如也。排查链路第一步确认事务是否提交。如果你用的是openSession()不带参数也没调用session.commit()数据肯定没落库。这是90%情况的原因。第二步确认查询的数据库是不是同一个。多数据源环境或者本地起了多个MySQL实例时很容易出现插入到A库、查询B库的乌龙。检查一下Jdbc URL的中数据库名。第三步排查触发器或事务拦截器。如果上面两步都没问题那说明确实有代码在事务未提交时做了回滚。检查项目中是否有AOP事务拦截比如切面定义了Transactional且默认rollbackFor某些异常。第四步确认数据库是否开启了自动提交。如果你用Navicat等客户端手动开了事务但没有提交查不到数据是正常的。4.3 坑三报错Invalid bound statement (not found)**表现**测试代码执行到mapper.insertUser(user)这一行就抛异常提示找不到绑定语句。排查链路这个已经是MyBatis新手三连坑之一了。按顺序排查打开XML文件检查mapper namespace...是否与接口的全限定名完全一致大小写、包路径都不能错。检查insert idinsertUser的id是否与接口方法名完全一致。检查XML文件是否被mybatis-config.xml中的mappers标签正确加载。如果用mapper resourcemapper/UserMapper.xml/方式确认资源路径正确如果配置了通配路径确认XML在classpath下。检查项目编译输出目录。用IDEA时如果XML文件在src/main/java下且没有在pom.xml中配置resources编译后XML不会出现在target/classes目录MyBatis自然找不到。最后如果项目整合了Spring检查MapperScan扫描的包路径是否覆盖了Mapper接口所在包。**我自己的排查习惯**先看target/classes目录下有没有对应的XML文件。文件不在一切免谈。4.4 坑四插入成功但主键没有回填user.getId() 是null**表现**影响行数为1数据库记录正常但测试代码里的user.getId()返回null。排查链路首先确认insert标签有没有写useGeneratedKeystrue。没写肯定不回填。确认keyPropertyid中的id是不是实体类属性名。如果你实体类里主键属性叫userId这里写id那也是白写。MyBatis按属性名反射调用setter方法属性名对不上就静默失败。确认数据库表主键确实自增。如果主键是业务自定义的比如手动赋值UUID或者有序IDuseGeneratedKeys是无能为力的得用selectKey或者其他方案。确认测试代码是在INSERT执行完之后打印getId。有人把打印放在mapper.insertUser()之前那当然是null这种属于低级错误但确实发生过。4.5 坑五插入时唯一键冲突报Duplicate entry**表现**第二次跑测试就报Duplicate entry test_user for key username_UNIQUE。这种坑其实说明你的表设计有唯一约束而你在表上建了username的唯一索引。本质上这是业务需求但如果是为了测试方便我会在测试代码里随机生成用户名避免重复user.setUsername(test_user_ System.currentTimeMillis());**更好的做法**如果测试需要反复执行且你有Spring事务的支持直接用Transactional让数据跑完就回滚。这是最干净的方案不需要每次改名字。5. 从添加功能测试延伸到三个进阶加固点添加功能测试通过后不意味着映射文件就写完了。在真实生产项目里还有三个进阶问题我这个系列后面也会单独讲这里先做个铺垫在测试环节就可以顺手验证。5.1 参数为null时的动态SQL处理现在的insertUser是固定五列字段插入。假设前端传来的email为空你会怎么做一种做法是让数据库的email字段允许NULL直接插入NULL值了事。但很多业务的数据库设计是email VARCHAR(100) NOT NULL此时如果你不传emailINSERT就报错。这时就需要动态SQLinsert idinsertUser parameterTypecom.example.entity.User useGeneratedKeystrue keyPropertyid INSERT INTO t_user trim prefix( suffix) suffixOverrides, if testusername ! nullusername,/if if testemail ! nullemail,/if if testcreateTime ! nullcreate_time,/if /trim trim prefixVALUES ( suffix) suffixOverrides, if testusername ! null#{username},/if if testemail ! null#{email},/if if testcreateTime ! null#{createTime},/if /trim /insert这样写传入的实体对象哪个字段不为空就插入哪一列。测试的时候就分别用全字段都有值email为空createTime为空三组case来验证。这是业务系统里最常用的插入写法但也请注意if判空要谨慎。比如username空字符串是能通过! null判断的如果你连空字符串都想去掉需要再加条件if testusername ! null and username ! username,/if这种细节非常有实际价值测试用例直接覆盖它不要只看单一场景。5.2 插入后事务控制的联动验证添加功能往往不是孤立的。典型的场景插入User后还要插入UserRole关联表。此时如果第二个INSERT失败第一个INSERT也应该回滚。测试这种联动可以用下面这种代码Test Transactional public void testInsertUserAndRole() { User user new User(); user.setUsername(tx_user_ System.currentTimeMillis()); userMapper.insertUser(user); // 故意构造一个会导致失败的操作 UserRole userRole new UserRole(user.getId(), null); // roleId为空 try { userRoleMapper.insertUserRole(userRole); Assert.fail(这里应该抛出SQL异常); } catch (Exception e) { // 期望的异常捕获 } }在Transactional下如果第二个插入抛出异常整个测试方法结束后事务会回滚第一个插入也不会真正落库。这能帮你验证单条SQL正确和整个业务操作正确的区别。值得反复强调的是MyBatis的单个SQL操作正常不代表你的业务逻辑正确。业务的正确性必须由事务机制来兜底。5.3 Mapper方法返回值含义的确认Mapper接口的insertUser返回int表示影响的行数。常规插入一行返回1。但有一些情况下返回的行数可能是其他值比如用了ON DUPLICATE KEY UPDATE语法时MySQL返回的affected rows不是1如果主键冲突走了更新可能返回21个insert尝试1个update。批量插入时返回的是成功插入的总行数。被触发器影响时返回值可能和实际插入行数不一致。所以在测试的时候不要硬编码断言assertEquals(1, rows)除非你能确保没有触发器、没有冲突更新逻辑。更稳妥的断言是Assert.assertTrue(rows 0);测试的断言本身要经得起真实业务的复杂性。初学者最喜欢assertEquals(1, rows)但这个断言在复杂业务下反而容易成为误报的来源。6. 测试中让MyBatis打印SQL日志的配置以及日志看不到时的处理测试添加功能时有一条SQL日志输出我觉得是必须的——否则你根本不知道MyBatis执行了什么SQL、参数填了什么值。这里我把两种主流配置方式都列出来。6.1 标准日志配置log4j2 / logbackMyBatis的日志是通过logImpl来控制的。在mybatis-config.xml中配置settings setting namelogImpl valueSLF4J/ /settings如果你的项目用的是LogbackSpring Boot默认把logImpl设为SLF4J会让MyBatis的日志输出走SLF4J门面由Logback处理。然后在Logback配置文件中给Mapper接口所在的包设置DEBUG级别logger namecom.example.mapper levelDEBUG/这样你就能看到类似下面的输出 Preparing: INSERT INTO t_user (username, email, create_time) VALUES (?, ?, ?) Parameters: junit_user(String), junitexample.com(String), 2024-06-01 12:00:00.123(Timestamp) Updates: 1这三行信息价值极高。Preparing告诉你最终发送给数据库的SQL长什么样Parameters告诉你每个占位符实际填的值Updates告诉你影响的行数。6.2 日志看不到时的排查方向如果测试代码运行后没有任何SQL日志输出按下面顺序排查确认配置了logImpl。很多教程只让你配SLF4J但你的项目里可能没有SLF4J绑定此时MyBatis反而会警告找不到SLF4J然后退回NoLoggingImpl自然啥也不打印。确认日志级别。如果Root logger级别是INFO而Mapper包没有单独设置DEBUGSQL日志会被过滤掉。确认用了哪个日志框架。如果你的项目用的是log4j2但依赖里没有log4j2的适配器配置会无效。确认没有在settings中重复设置logImpl。多个配置源比如Spring Boot的application.yml里也配了mybatis.configuration.log-impl冲突也会导致日志行为异常。如果你的项目是Spring Boot整合的MyBatis更推荐直接在application.yml中配置mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImplStdOutImpl是MyBatis自带的日志实现会把SQL直接打印到控制台不依赖Logback或log4j2。它是在你搞不定日志框架适配时最快速、最粗暴的解决方案。缺点是正式生产环境不建议用控制台输出太吵且没有日志文件归档能力。6.3 通过SQL日志反向验证参数是否正确填充SQL日志的价值不只是看有没有执行更重要的是看参数填充是否准确。有一次我排查一个插入异常同事说插入报错了但SQL看起来没错。我看了一眼日志 Preparing: INSERT INTO t_user (username, email, create_time) VALUES (?, ?, ?) Parameters: null(String), xxxexample.com(String), 2024-06-01 12:00:00.123(Timestamp)问题一目了然username传了null而表定义里username是NOT NULL插入自然失败。如果他没有打印日志就只能在异常堆栈里翻SQLIntegrityConstraintViolationException排查效率差很多。所以我在测试环境中一定会把SQL日志打开等测试全部通过后再根据需求决定是否在生产环境关闭。7. 测试后的数据清洁手动清理、事务回滚与独立测试库的三种策略测试添加功能最容易被人忽略的是测试数据污染问题。每跑一次测试就往t_user插一条记录跑十次就多了十条垃圾数据。长此以往测试数据越来越乱总有一天会把某个唯一索引撑爆。我见过三种处理策略按推荐程度排序7.1 单独建测试库最推荐在MySQL里再建一个mybatis_test库结构复制mybatis_demo库。测试环境连接mybatis_test库随便折腾坏了就重建。# application-test.yml spring: datasource: url: jdbc:mysql://localhost:3306/mybatis_test username: root password: your_password这种策略的好处是测试和生产数据完全隔离不需要在代码里做任何特殊处理。唯一要注意的是别把测试库的连接信息带上生产环境。如果你用Maven的profile区分环境testprofile下才启用这个配置。7.2 用事务回滚快速但有限制如前面所说在测试方法上加Transactional测试结束后事务回滚插入的数据自动消失。这种方式对单条插入、单事务场景非常好用但有两个限制如果测试方法内部自己提交了事务比如手动调用TransactionTemplate的PROPAGATION_REQUIRES_NEW外层回滚就失效了。如果测试的代码会向消息队列发送消息、触发异步任务这些副作用不会因为事务回滚而撤销。7.3 手动DELETE清理最后手段有时候你确实不想用事务回滚又不想单独建库——比如你测试的就是事务提交后的真实行为。此时可以在测试方法最后显式删除测试数据Test public void testInsertAndCleanup() { User user new User(); user.setUsername(temp_user_ System.currentTimeMillis()); userMapper.insertUser(user); Integer insertedId user.getId(); Assert.assertNotNull(insertedId); // 最后清理 userMapper.deleteUserById(insertedId); }这里要求你提前准备好deleteUserById方法。作为测试代码这种自助式清洁完全可以接受。但我想提醒一点不要在测试中断言成功之前就清理数据。如果你在Assert之前执行了DELETE后面断言失败时数据已被清理你就无法在数据库中核对当时的实际状态了。顺序一定是断言验证 → 清理。8. 我自己做添加功能测试时的一些习惯和体会最后这部分算是我个人的经验沉淀。测试添加功能做到第六篇该给的代码和思路都给过了。这里分享几个我长期坚持的习惯每个都是踩坑踩出来的。第一测试数据尽量用动态值。用户名、邮箱、手机号这些字段在测试时拼上时间戳或UUID后缀。除非你刻意测试唯一约束冲突否则不要让测试数据之间有交集。这样即使事务回滚失效也不会干扰下一次测试运行。String uniqueSuffix UUID.randomUUID().toString().substring(0, 8); user.setUsername(tester_ uniqueSuffix);第二每个Mapper的新增方法第一次测试都用Debug模式跑。不要直接Run。用Debug模式在mapper.insertUser(user)这一行打断点一步步看参数对象的值看SQL预编译时的参数填充看返回值。Debug模式下你能直观看到user.getId()在insert前后从 null 变成具体数字的过程这个印象一旦建立你对MyBatis主键回填机制的理解就彻底到位了。第三测试类不要和业务类放一个包路径的产出里。Maven的src/test/java目录就是给测试代码用的测试代码可以同名同包但放在src/test/java下。编译时测试代码不会被打进最终产物避免了测试代码污染生产jar包。第四看到Updates: 0不要慌先查条件。对于INSERT来说Updates: 0极少见但如果插入的是空集合比如批量插入传了一个空listMyBatis可能连SQL都不会执行。针对这种情况测试里应该专门加一条传空list验证不报错的用例确认行为符合预期。第五也是最重要的每一次添加功能测试都要同时验证插入前的状态和插入后的状态。插入前的状态是指这条记录在数据库中确实不存在或者有一个可预期的初始行数插入后的状态是指记录真的存在了且关键字段值正确。只盯着影响行数你只是在验证代码不报错不是在验证功能正确。9. 关于测试添加功能这篇的收尾下一步往哪走搭建MyBatis框架这个系列到第六篇为止你已经完成了从配置文件、Mapper接口、XML映射到添加功能完整测试的闭环。这是整个框架里最基础、但也是最核心的一环——之后的查询功能、更新功能、删除功能在原理上都和添加功能高度相似区别主要在于SQL语法本身和返回值处理。下一步我建议按顺序做两件事先自己动手把selectUserByIdupdateUserByIddeleteUserById这三个基础功能补上用同样的测试方法验证一遍。这是对增删改查闭环的补全越早完成你对MyBatis整体工作流的理解会越扎实。然后再深入做一件事动态SQL。在有if判断插入列的用法里你会真正体会到MyBatis相比原生JDBC的优势所在。到那一篇的时候测试的复杂度也会上一个台阶——需要覆盖更多分支组合。添加功能是所有数据操作的地基测试则是让地基真正变扎实的手段。这一篇把测试过程中可能遇到的主要问题都过了一遍如果你在实操中碰到我没提到的报错别急着查搜索引擎先看SQL日志、先断点调试、先检查XML配置——大部分问题都在这三个环节里露出马脚。
阅读完成 · 觉得有帮助?