是不是已经按捺不住想要动手编写一个小系统的心情了?先不要着急,在动手之前,我送你3个锦囊(现在就可以打开看的那种)——单元测试、异常处理和日志。
单元测试可以让你的代码更加健壮;异常处理可以让意外对系统的伤害降到最低;日志可以帮助你在系统出现问题后更快地修复系统
代码的护身符——单元测试
写单元测试并不难,但写好单元测试不容易。下面我们来看看一个合格的单元测试应该具备哪些特质。
一个单元测试的自我修养
作为一个单元测试,要明确自己的定位,时时刻刻谨记——做一个合格的单元测试。那么,我们来看一下单元测试具备哪些素质才能称为一个合格的单元测试。
· 无副作用:单元测试不能对业务代码造成影响
· 可重复运行:多次运行结果一致
· 独立且完整:单元测试不依赖外部环境或其他模块的代码
前面两条很好理解,那么什么是“独立且完整”呢?例如,我们要为Service层的一个方法写单元测试,那么在运行这个单元测试时,就不能真的去访问数据库(因为与数据库交互的代码在Dao层,Service层的单元测试不能依赖Dao层),这就是“独立”。虽然不能访问数据库,但是需要保证整个流程可以正确、完整地执行,这就是“完整”。
那么我们如何做到“独立且完整”呢?答案就是——Mock。市面上有很多Mock框架,如Mockito、Jmock、easyMock等。借助这些工具,我们可以很轻松地Mock出我们想要的依赖。
为什么要写单元测试
为什么要写单元测试?我们决定是否做一件事,通常需要看做这件事的回报是否大于投入。写单元测试也不例外,下面我们来对比一下。
写单元测试需要的投入
写单元测试需要花费额外的时间。
写单元测试带来的回报
· 提升代码的可靠性
· 可以帮助你更早地解决Bug
· 可以让新人更快熟悉代码
· 为将来的重构保驾护航
写单元测试除需要花费额外的时间以外,好像并没有什么其他的缺点了。而写单元测试能给我们带来很多好处。提升代码的可靠性是肯定的。单元测试可以降低测试以后的Bug率,提升了代码的可靠性,同时也会让其他人觉得你是一个非常靠谱的人。
写单元测试可以让问题更早地暴露出来,从而更早得到解决。更早地解决Bug有什么好处呢?Google曾经就这个问题给出了一个参考——同一个Bug在编写代码的阶段修复需要5美元,而当整个工程构建完成以后再修复则需要50美元,等到集成测试的时候再修复,则会花费500美元,到了系统测试的时候就需要花费5000美元了。那么,如果上线以后才发现这个Bug,修复它需要多高的成本呢?
几乎没有什么比接手别人写的代码更令人难过的事情了。想要快速理解别人写的代码,最直接的方式就是执行Debug操作。对于小型系统还好,而对于大型系统来说,想要让它在本地运行起来都是一件令人头疼的事情,更别说调试代码了。如果系统有比较完备的单元测试,情况就不一样了。因为单元测试“独立且完整”,所以我们根本不需要启动整个工程,只需要按需调试即可。
如果有比接手别人写的代码更令人难过的事情,那一定是重构别人的代码。我们在维护那些遗留的代码时如履薄冰,面对老旧又臃肿的代码时束手无策,虽然曾经有过一万次想要重构的念头,但是被一万零一次担心改一处而导致整个系统崩溃的念头压了下去。而如果这个系统有很好的单元测试保驾护航,重构起来就会轻松很多,因为每一次改动都可以通过单元测试来验证它的正确性。
写单元测试真的会花费更多时间吗
前文曾提到,写单元测试有一个缺点——需要花费额外的时间。但真的是这样吗?如果你已经写完代码了,甚至项目上线前才开始补写单元测试,那么写单元测试的确会花费更多时间,因为你把它当作负担了。但实际上单元测试是我们的工具,可以用来提高代码可靠性、更早地修复Bug、更快地熟悉代码、更好地重构代码。也就是说,给你一把斧头,你却不用它砍柴,而只是背211着,那它当然是你的负担了,你当然也不愿意花时间打磨它了,道理就是这么简单!
当你开始正确对待单元测试以后,就会发现你写代码的能力也会随之提升,因为要写出更易于进行单元测试的业务代码,需要更好的程序设计能力。代码写得越好,写单元测试就越容易。想要单元测试写起来更顺畅,就需要不断提高程序设计能力,两者相辅相成、相得益彰
Junit
Junit是Java的一个单元测试框架,也是Spring Boot默认的单元测试工具。我们先来看一下Junit的几个核心概念和常用注解。Junit核心概念及其说明如表7-1所示。
Junit常用注解及其说明如表7-2所示。
@BeforeAll、@AfterAll、@BeforeEach、@AfterEach这几个注解很相似,无法通过语言描述让人体会到它们之间的区别。下面我们通过一个示例来体会一下,代码如下:
可以看到,@BeforeAll和@AfterAll在一次测试执行中只会执行一次,而@BeforeEach和@AfterEach则会在每个测试方法的执行前/后都执行一次。其他几个注解比较好理解,源码中也给出了相应的示例,仅供参考。