UT开发总结
创始人
2025-05-28 15:18:20
0

UT

如果打算做一些现有代码的重构,强烈建议重构要有UT保障,毕竟,没有UT的重构就是裸奔…

UT的作用

  • 提前发现问题,避免将问题推迟到sit甚至更后期;
  • 修改问题单或者重构时更有保障;
  • 底层三方包升级保障

UT规范

类命名

{待测试类名} + “Test”后缀

例如:

CustomerSvcMgrServiceTest

函数命名

“test” + {被测试的方法名称} + “_Should” + {预期结果} + “_When” + {条件}

例如:

testXXX_ShouldSuccess_WhenDoingSth
testXXX_ShouldSuccess

如果ut方法名无法取得较好的自注释效果,可对ut方法增加注释说明。

UT讨论

@Mock和@MockBean

@Mock是Mockito提供的注解,用于创建待测类的模拟对象。

@MockBean不仅是创建待测类的模拟对象,还将这个模拟对象加到spring上下文(ApplicationContext)里。如果我们使用的是SpringBootTest做集成测试,且要mock一个已有的bean(@Service、@Component等注解标注),应该使用@MockBean,而不是@Mock。

@MockBean一般用于成员级别,还有一个@MockBeans的注解,最终效果跟@MockBean差不多,必须放到类级别,然后在成员字段引入的时候依然用@Autowired或@Inject,比如下例:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartupApplication.class)
@Slf4j
@MockBeans({@MockBean(IPhaseService.class)})
public class PhaseCacheHelperTest {@Autowiredprivate IPhaseService phaseService;@Testpublic void testGetByPhaseCode_ShouldLoadFromDs() {...Mockito.when(phaseService.findPhase(phaseCode)).thenReturn(resp);PhaseVO actualVo = PhaseCacheHelper.getByPhaseCode(phaseCode);Assert.assertNotNull(actualVo);Assert.assertEquals(actualVo.getPhaseName(), expectVo.getPhaseName());Mockito.verify(phaseService, Mockito.times(1)).findPhase(phaseCode);}

用MockBeans注解的时候,测试框架会提前把IPhaseService类mock化,再用通常的 @Autowired注解注入,它不会像@Mockbean那样走MockitoPostProcessor的后处理过程。这个后处理过程在有些情况下会产生多次注入的错误:

the field private cannot have an existing value

关于Mockito的参数匹配,参看:

https://blog.csdn.net/listeningsea/article/details/123224131

我们用的比较多的是精确匹配和模糊匹配。精确匹配要注意,如果传入的是一个自定义对象的话,要求这个对象有equals方法,不然做不到精确匹配。

@SpringBootTest的涵义

@SpringBootTest为我们的测试提供了spring boot支持,它启动了spring上下文,提供的是E2E的测试能力,所以本质上它已经属于IT的范围了。我们将IT放到UT阶段做,提前发现问题,并无不妥,不用拘泥于概念。

说明一下,@RunWith(SpringRunner.class)是junit4的写法,junit5(jupyter)下已经没有这个注解了。

DB要不要mock

我理解,如果将@SpringBootTest认为是IT的话,DB可以不mock,连接真实环境能提前发现一些很明显的sql语法错误,一个典型的场景是为mybatis mapper接口写测试。当然,如果测试的重点是代码逻辑,或者要构造一些DB异常,那DB还是要mock的。

void函数的测试

void函数既然无法检查返回值,可以视情况检查副作用,比如一些潜在的数据变化、调用次数。

UT开发常见问题

  • UT函数名随意取,比如testXXX1,testXXX2,建议按规范来,要求看到名字能猜出要测什么;
  • UT里只有方法调用,而没有assertion断言;
  • 一个UT只测一种场景,不要将所有场景的测试放到一个UT里;
  • 由于三方依赖,只测失败用例,没有成功用例(注意:这时UT代码覆盖率依然可能是达标的!)。这时可以适当借助mock方法来模拟成功用例;
  • UT代码也要有基本的设计和代码质量保证,适当做一些公共抽取,避免出现大量重复的测试代码
  • 跑去测试getter和setter方法,这样毫无意义!
  • 写全部类的UT工作量太大,此时,可测试你最关心的部分、测试你觉得最容易出错的部分
  • 忽略边界条件的UT,比如数组的边界测试

在UT中使用H2

既然DB不mock,那就得连真实数据库,可能的做法是:要么本地搭一个mysql,要么连接远程mysql。前者有搭建工作量,后者受制于环境数据,跑UT容易出问题。

因此,考虑在UT中引入H2内存数据库,外加flyway来初始化数据库表结构。

我们先做一个bean来在dataSource就绪时立刻执行建表语句:

@Component
@Slf4j
@DependsOnDatabaseInitialization
public class DBPrepare {private static final String H2SQL_DIR = "/tmp/H2SQL";@Injectprivate DataSource dataSource;@PostConstructpublic void init() {// only H2 db use flywayif (isH2DB()) {log.info("DBPrepare");genH2SqlScript();String[] locations = new String[] {"filesystem:" + H2SQL_DIR + "/"};Flyway flyway = buildFlyway(dataSource, "prjname", locations);try {flyway.migrate();log.info("DBPrepare success");} finally {cleanH2SqlScript();}}}...
}

由于原始的mysql脚本在H2里执行要做一些调整,所以我们写了genH2SqlScript来做这个转换的事情。
转换的逻辑看下节。

H2与MySQL的兼容性

接着,要看H2与mysql的兼容性,幸运的是H2可以设置MYSQL模式:

MODE=MySQL

以兼容mysql建表语句。不过,即使在兼容mysql的模式下,也有些不同之处需注意:

  • PRIMARY KEY后不能使用USING BTREE和COMMENT
  • UNIQUE KEY或KEY后不能用COMMENT
  • ENGINE = InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci 在H2的1.4.200版本不能支持,不过在随后的新版本里是支持的
  • replace into在H2里不支持,改用ON DUPLICATE KEY UPDATE

H2连接字符串

我们设置的H2连接字符串为:

jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MySQL;DATABASE_TO_LOWER=TRUE

选项说明如下:

By default, closing the last connection to a database closes the database. For an in-memory database, this means the content is lost. To keep the database open, add DB_CLOSE_DELAY=-1 to the database URL. To keep the content of an in-memory database as long as the virtual machine is alive, use jdbc:h2:mem:test;DB_CLOSE_DELAY=-1By default, a database is closed when the last connection is closed. However, if it is never closed, the database is closed when the virtual machine exits normally, using a shutdown hook. In some situations, the database should not be closed in this case, for example because the database is still used at virtual machine shutdown (to store the shutdown process in the database for example). For those cases, the automatic closing of the database can be disabled in the database URL. The first connection (the one that is opening the database) needs to set the option in the database URL (it is not possible to change the setting afterwards). The database URL to disable database closing on exit is:
String url = "jdbc:h2:~/test;DB_CLOSE_ON_EXIT=FALSE";

参考:http://www.h2database.com/html/features.html

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...