单元测试应用
开发人员写的常常是“单元测试”,但其实可以细分成 单元测试
和 集成测试
两个。
单元测试
单元测试:是指对软件中的最小可测试单元进行检查和验证。
单元测试是编写测试代码,用来检测特定的、明确的、细颗粒的功能。单元测试并不一定保证程序功能是正确的,更不保证整体业务是准确的。
单元测试不仅仅用来保证当前代码的正确性,更重要的是用来保证代码修复、改进或重构之后的正确性。
单元测试不验证应用程序代码是否和外部依赖正常工作。它聚焦与单个组件并且 Mock 所有和它交互的依赖。例如,方法中调用发短信的服务,以及和数据库的交互,我们只需要 Mock 假执行即可,毕竟测试的焦点在当前方法上。
单元测试的特点:
- 不依赖任何模块。
- 基于代码的测试,不需要在 ApplicationContext 中运行。
- 方法执行快,500ms以内(也和不启动 Spring 有关)。
- 同一单元测试可重复执行N次,并每次运行结果相同。
单元测试任务包括
- 接口功能测试:用来保证接口功能的正确性。
- 局部数据结构测试(不常用):用来保证接口中的数据结构是正确的
- 比如变量有无初始值
- 变量是否溢出
- 边界条件测试
- 变量没有赋值(即为NULL)
- 变量是数值(或字符)
- 主要边界:最小值,最大值,无穷大(对于DOUBLE等)
- 溢出边界(期望异常或拒绝服务):最小值-1,最大值+1
- 临近边界:最小值+1,最大值-1
- 变量是字符串
- 引用“字符变量”的边界
- 空字符串
- 对字符串长度应用“数值变量”的边界
- 变量是集合
- 空集合
- 对集合的大小应用“数值变量”的边界
- 调整次序:升序、降序
- 变量有规律
- 比如对于Math.sqrt,给出n2-1,和n2+1的边界
- 所有独立执行通路测试:保证每一条代码,每个分支都经过测试
- 代码覆盖率
- 语句覆盖:保证每一个语句都执行到了
- 判定覆盖(分支覆盖):保证每一个分支都执行到
- 条件覆盖:保证每一个条件都覆盖到true和false(即if、while中的条件语句)
- 路径覆盖:保证每一个路径都覆盖到
- 相关软件
- Cobertura:语句覆盖
- Emma: Eclipse插件Eclemma
- 代码覆盖率
- 各条错误处理通路测试:保证每一个异常都经过测试
集成测试
集成测试:在单元测试的基础上,将所有模块按照设计要求组装成为子系统或系统,进行集成测试。
集成测试主要用于发现用户端到端请求时不同模块交互产生的问题。集成测试范围可以是整个应用程序,也可以是一个单独的模块,取决于要测试什么。
在集成测试中,我们应该聚焦于从控制器层到持久层的完整请求。应用程序应该运行嵌入服务(例如:Tomcat)以创建应用程序上下文和所有 bean。这些 bean 有的可能会被 Mock 覆盖。
集成测试的特点:
- 集成测试的目的是测试不同的模块一共工作能否达到预期。
- 应用程序应该在 ApplicationContext 中运行。Spring boot 提供 @SpringBootTest 注解创建运行上下文。
- 使用 @TestConfiguration 等配置测试环境。
JUnit4 & JUnit5
常见区别有:
- JUnit4 所需 JDK5+ 版本即可,而 JUnit5 需 JDK8+ 版本,因此支持很多 Lambda 方法。
- JUnit 4将所有内容捆绑到单个jar文件中。Junit 5由3个子项目组成,即JUnit Platform,JUnit Jupiter和JUnit Vintage。核心是JUnit Jupiter,它具有所有新的junit注释和TestEngine实现,以运行使用这些注释编写的测试。而JUnit Vintage包含对JUnit3、JUnit4的兼容,所以spring-boot-starter-test新版本pom中往往会自动exclusion它。
- SpringBoot 2.2.0 开始引入 JUnit5 作为单元测试默认库,在 SpringBoot 2.2.0 之前,spring-boot-starter-test 包含了 JUnit4 的依赖,SpringBoot 2.2.0 之后替换成了 Junit Jupiter。
**JUnit4 **
JUnit4通过注解的方式来识别测试方法。目前支持的主要注解有:
@BeforeClass
全局只会执行一次,而且是第一个运行,是static方法@Before
在测试方法运行之前运行@Test
测试方法@After
在测试方法运行之后允许@AfterClass
全局只会执行一次,而且是最后一个运行,是static方法@Ignore
忽略此方法
JUnit5 的注解
功能 | JUnit4 | JUnit5 |
---|---|---|
声明一种测试方法 | @Test | @Test |
在当前类中的所有测试方法之前执行 | @BeforeClass | @BeforeAll |
在当前类中的所有测试方法之后执行 | @AfterClass | @AfterAll |
在每个测试方法之前执行 | @Before | @BeforeEach |
在每个测试方法之后执行 | @After | @AfterEach |
禁用测试方法/类 | @Ignore | @Disabled |
测试工厂进行动态测试 | NA | @TestFactory |
嵌套测试 | NA | @Nested |
标记和过滤 | @Category | @Tag |
注册自定义扩展 | NA | @ExtendWith |
RunWith 和 ExtendWith
在 JUnit4 版本,在测试类加 @SpringBootTest
注解时,同样要加上 @RunWith(SpringRunner.class)
才生效,即:
@SpringBootTest
@RunWith(SpringRunner.class)
class ServiceTest {
...
}
但在 JUnit5 中,官网告知 @RunWith
的功能都被 @ExtendWith
替代,即原 @RunWith(SpringRunner.class)
被同功能的 @ExtendWith(SpringExtension.class)
替代。但 JUnit5 中 @SpringBootTest 注解中已经默认包含了 @ExtendWith(SpringExtension.class)。
因此,在 JUnit5 中只需要单独使用 @SpringBootTest
注解即可。其他需要自定义拓展的再用 @ExtendWith
,不要再用 @RunWith
了。
具体JUnit5 的使用这篇不做详细介绍,该题应该另开一篇文章来描述
值得参考的文章:(5条消息) junit5 入门系列教程-01-junit5 简单入门例子_老马啸西风的博客-CSDN博客_junit5教程
使用Mockito
Mock 框架有很多,除了传统的 EasyMock、Mockito以外,还有PowerMock、JMock、JMockit等。个人一般选用用 Mockito和 PowerMock,因为 Mockito 在社区流行度较高,而且是 SpringBoot 默认集成的框架。用PowerMock来处理mock处理不了的static静态方法的mock。
Mockito 框架中最核心的两个概念就是 Mock 和 Stub。测试时不是真正的操作外部资源,而是通过自定义的代码进行模拟操作。我们可以对任何的依赖进行模拟,从而使测试的行为不需要任何准备工作或者不具备任何副作用。
具体使用方法在官方文档中描述很全面很详细,这边不做赘述,链接如下
官方文档:GitHub - hehonghui/mockito-doc-zh: Mockito框架中文文档
代码覆盖率
在了解搜索相关内容时,发现代码覆盖率这块内容时不曾了解的,该方面内容待学习和补足。切入点: Jacoco,Eclmama