单元测试
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。在Java中单元测试的最小单元是类。
为什么写单元测试?
- 保证或验证实现功能。
- 保护已经实现的功能不被破坏。
只针对某一个功能点写测试,比如独立测试某类中的某个方法。但这个类和方法本身不是独立的,可能会去调用其他的类或方法。我们可以采用Mockito和PowerMock两种工具,来虚拟那些外部的、不容易构造的对象:
Mockito:适用于大多数标准的单元测试。
PowerMock
- PowerMock在Mockito的基础上,额外增加了更多case(如static Method,final method, 枚举类, private method和Constructor)
- PowerMock写法与Mockito基本相同,这是因为PowerMock是从Mockito的一个特殊的API衍化而来。比如当添加PowerMock Junit-module的maven依赖时时,其实还要导入Mockito-API。
1 | <dependency> |
Mockito
通过创建mock或spy对象,并制定具体返回规则来实现模拟的功能:
- mock对象对于未指定处理规则的调用,会按方法返回值类型返回该类型的默认值(如int、long则返回0,boolean则返回false,对象则返回null,void则什么都不做)
- spy对象在未指定处理规则时则会直接调用真实方法。
1 | public class OpenServiceImplTest { |
PowerMock
1 | @RunWith(PowerMockRunner.class) |
Spring Boot
SpringBoot
也是基于Junit
进行单位测试的。
spring-boot-starter-test这个依赖中包含了:
- JUnit:Java 应用程序单元测试标准类库。
- Spring Test & Spring Boot Test:Spring Boot 应用程序功能集成化测试支持。
- Mockito:一个Java Mock测试框架。
- AssertJ:一个轻量级的断言类库。
- Hamcrest:一个对象匹配器类库。
- JSONassert:一个用于JSON的断言库。
- JsonPath:一个JSON操作类库。
1 | <dependency> |
打开IDEA,在任意类名、任意接口名上,按ctrl+shift+t
,选择Create New Test
。然后根据提示操作(默认即可),点击确认,就在项目的/test/java下的对应包里,生成了与类对应的测试类。
或者,在任意类名、任意接口名上,按Alt+Insert
,然后选择Test
。然后根据提示操作(默认即可),点击确认,就在项目的/test/java下的对应包里,生成了与类对应的测试类。
如果没有“Create New Test”,请更新idea版本或者在plugin中安装插件JUnitGenerator V2.0(仅支持到JUNIT4、3等老版本。idea升级后新版本不需要安装此插件)。
参阅:Spring Boot:快速创建单元测试_e6486883的博客-CSDN博客_springboot创建测试单元
代码覆盖率测试工具
Intellij IDEA集成了三种分析单元测试覆盖率的工具,包括IntelliJ IDEA、JaCoCo和Emma。
- IntelliJ IDEA,是idea默认自带的插件,统计出来的覆盖率只包含classes、method、line,不详细
- JaCoCo,最常用。需要首先在Idea中选择JaCoCo模式,而后在项目的maven polm文件中进行依赖配置。
IntelliJ IDEA使用步骤
Run → Edit Configurations
选择测试范围:指定要测试的包路径或类文件等
Code Coverage
选项卡可以调整覆盖率设置。- Tracing mode模式会增加消耗,但测量会更精确。
- 可以设置记录覆盖信息的类或包,即最后生成的测试覆盖率报告里包含了哪些类或包。
- 右键选择
Run 'All Tests' with Coverage
,开始运行所有测试用例。运行完后,IDE将会在Coverage
工具窗显示所有include进来的包/类的覆盖率数据,也可以导出测试覆盖率报告 。
- 红色方块:没有覆盖(在这一行中没有分支被执行)
- 黄色方块:部分覆盖(这一行的分支中只有一部分被执行)
- 绿色方块:完全覆盖(这一行的所有分支都被执行)
JaCoCo使用步骤
- Run → Edit Configurations中设置为JaCoCo
- maven配置
添加依赖:配置JaCoCo插件一定注意和JDK版本的对应关系,如果是jdk1.8,则插件一定要用最新版,不然会报错误。g’g
1 | <dependency> |
配置plugins
1 | <plugin> |
刷新maven,会发现多了一个插件:
arget文件夹中会多出一个site文件夹(如果没有点击上述jacoco:report)。点击里面的index.html文件,用浏览器打开即可看到测试报告。
- maven配置
执行mvn install
或者 mvn test
命令,获得 JaCoCo的统计数据。执行完以后,target/site/jacoco/目录下会生成一个index.html文件,这是统计数据总览页面,可以在浏览器打开查看。
集成多模块报告
执行 mvn clean package
(clean install)命令后,在项目的 ROOT\target\site\目录会生成 jacoco目录
断言
Assert
- Assert.assertEquals 对比两个值相等
- Assert.assertNotEquals 对比两个值不相等
- Assert.assertSame 对比两个对象的引用相等
- Assert.assertArrayEquals 对比两个数组相等
- Assert.assertTrue 验证返回是否为真
- Assert.assertFlase 验证返回是否为假
- Assert.assertNull 验证null
- Assert.assertNotNull 验证非null
Assertions
1 | <dependency> |
「译」JUnit 5 系列:基础入门 - Linesh 林从羽 - OSCHINA - 中文开源技术交流社区
JUnit 单元测试断言推荐 AssertJ - 星朝 - 博客园 (cnblogs.com)
走进Java接口测试之流式断言库AssertJ_Mo小泽的技术博客-CSDN博客
1 | import org.assertj.core.api.Assertions; |
1 | public static String getRespErrorMessage(ReturnMessage<?> returnMessage) { |
1 | Expected :java.util.Arrays$ArrayList<[SimpleResolver [/8.8.8.8:53], SimpleResolver [/114.114.114.114:53]]> |
https://blog.csdn.net/qq_34802416/article/details/84192236
1 | import org.junit.Test; |
私有方法
1 | @RunWith(PowerMockRunner.class) |
私有属性
1 | @RunWith(PowerMockRunner.class) |
Set断言
1 | @Test |
Junit常用注解说明
- @BeforeClass 加上这个注解,则该方法会第一个执行(在所有方法中),且方法要加上关键词static,是一个static方法
- @Before 带上@Test的方法执行前会执行该方法
- @Test 加在待测试的方法前面,@Test(timeout = 1000)设置1000毫秒后超时
- @After 带上@Test的方法执行完毕后会执行该方法
- @AfterClass 加上这个注解,则该方法最后一个执行(在所有方法中),同样,方法要加上关键词static,是一个static方法
一个单元测试类执行顺序为:
@BeforeClass -> @Before -> @Test -> @After -> @AfterClass
单元测试之使用H2 Database模拟数据库环境 单元测试之使用H2 Database模拟数据库环境 | Coderec’s Blog
单元测试之Mockito与PowerMock https://www.jianshu.com/p/51930cc5dcf9
https://blog.csdn.net/qisibajie/article/details/79068086
https://juejin.cn/post/6844903711483887623
Mockito与PowerMock的使用基础教程
https://juejin.cn/post/6844903711483887623
WEB测试
在Spring Boot项目里面可以直接使用JUnit对web项目进行测试,Spring 提供了“TestRestTemplate”对象,使用这个对象可以很方便的进行模拟请求。
Web测试只需要进行两步操作:
- 在@SpringBootTest注解上设置“ebEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT”随机端口;
- 使用TestRestTemplate进行post或get请求;
示例代码如下:
1 | .class) (SpringRunner |
其中getForObject的含义代表执行get请求,并返回Object结果,第二个参数设置返回结果为String类型,更多的请求方法:
- getForEntity:Get请求,返回实体对象(可以是集合);
- postForEntity:Post请求,返回实体对象(可以是集合);
- postForObject:Post请求,返回对象;
数据库测试
测试数据操作时,如果给测试类上添加@Transactional
,会让数据操作都在内存中完成,并不会真正的commit到数据库,将数据持久化操作截断。
- 好处是:既可以测试数据操作方法,又不会污染数据库。
- 注意点是:数据持久化的过程不再真实,没有了commit的过程。从而会导致——
- 无法保证 Entity 之间关联关系,唯一索引和主外键关联的准确性。
- 无法保证 Entity 创建时间、更新时间和版本化(乐观锁)的赋值逻辑的准确性。
- 无法保证 Entity 中有 @Transient 注解的属性的赋值逻辑的准确性。
- 测试的数据不是真实场景存在的问题。
- 测试中,单个事务中的准备数据,无法在多线程中共享。
1 |
|
集成测试
- 集成多个功能
- 加载 spring 框架,要启整个 Spring
1.在class上加入@SpringBootTest 和@RunWith(SpringRunner.class)两个注解即可。
@SpringBootTest
:获取启动类,加载配置,寻找主配置启动类(被 @SpringBootApplication 注解的类)@RunWith(SpringRunner.class)
:让JUnit运行Spring的测试环境,获得Spring环境的上下文的支持
1 | RunWith(SpringRunner.class) |
增加@Transactional支持回滚
1 | RunWith(SpringRunner.class) |
增加@Transactional支持回滚的同时,单独设置方法不回滚(正常提交)1
2
3
4
5
6
7
8
9
10
11
12
13
14RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class Test() {
// @Transactional的前提下,实现真正提交事务(即指定@Rollback(false))
@Test
@Rollback(false)
void testCommit(){}
// @Transactional的前提下,默认会回滚(即@Rollback(True))
@Test
void testRollBack(){}
}
2.在测试方法上加上@Test注解。
1 |
|
测试私有方法
1 |
|
1 | val targetClass = DomainServiceImpl::class.java |
ReflectionUtils
是Spring
中一个常用的类,属于spring-core
包;
ReflectionTestUtils
则属于spring-test
包。
两者功能有重叠的地方,而ReflectionUtils
会更强大。在单元测试时使用ReflectionTestUtils
,能增加我们的便利性。
处理
UT中使用ReflectionTestUtils.setField不能mock掉依赖问题解决_苦行僧-CSDN博客_reflectiontestutils.setfield
service - ReflectionTestUtils not working with @Autowired in Spring Test - Stack Overflow
获取对象的成员变量:
1 | public static Object getField(@Nullable Object targetObject, String name) |
给对象注入成员变量:
1 | public static void setField(Class<?> targetClass, String name, @Nullable Object value) |
调用成员方法:
1 | public static <T> T invokeMethod(Object target, String name, Object... args) |
单元测试时测试一个private私有方法时,我们第一想法可能是用java反射机制。
…
Method method = clazz.getDeclaredMethod(methodName, classes)
method.setAccessible(true);
method.invoke(obj, objects)
Spring 有一个好用的测试工具类ReflectionTestUtils
…
ReflectionTestUtils.invokeMethod(Object target, String name, Object… args)
即可完成调用私有方法。
maven依赖:
————————————————
版权声明:本文为CSDN博主「lijie2049」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lijie2049/article/details/84732723
1 | Unexpected exception, expected<exception.DomainException> but was<java.lang.reflect.InvocationTargetException> |
反射,异常处理
What Causes java.lang.reflect.InvocationTargetException? | Baeldung
1 | val targetClass = DomainServiceImpl::class.java |
1 | private fun getSubDomainPOList(domainName: String, withUser: Boolean, userId: String? = null): List<DomainPO>? |
Java类型与Kotlin类型对应关系_WongKyunban的博客-CSDN博客
MockMvc是由spring-test包提供,实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,使得测试速度快、不依赖网络环境。同时提供了一套验证的工具,结果的验证十分方便。
(1) mockMvc.perform执行一个请求。
(2) MockMvcRequestBuilders.get(“XXX”)构造一个请求。
(3) ResultActions.param添加请求传值
(4) ResultActions.accept()设置返回类型
(5) ResultActions.andExpect添加执行完成后的断言。
(6) ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情,比如处使用print()输出整个响应结果信息。
(7) ResultActions.andReturn表示执行完成后返回相应的结果。