巩义网站建设费用,百度竞价排名平台,做湲兔费网站视颍,wordpress 淘宝客 主题在没有代码生成工具或尝试一门新的 ORM框架时#xff0c;当我们希望不去另外写 Service 和 Controller 来验证 DAO 层的代码不希望只通过接口请求的方式来验证时#xff0c;这时候单元测试的方式就可以帮助我们满足这一需求。 在我们开发Web应用时#xff0c;经常会直接去观…在没有代码生成工具或尝试一门新的 ORM框架时当我们希望不去另外写 Service 和 Controller 来验证 DAO 层的代码不希望只通过接口请求的方式来验证时这时候单元测试的方式就可以帮助我们满足这一需求。 在我们开发Web应用时经常会直接去观察结果进行测试。虽然也是一种方式但是并不严谨。作为开发者编写测试代码来测试自己所写的业务逻辑是以提高代码的质量、降低错误方法的概率以及进行性能测试等。经常作为开发这写的最多就是单元测试。引入spring-boot-starter-testSpringBoot的测试依赖。该依赖会引入JUnit的测试包也是我们用的做多的单元测试包。而Spring Boot在此基础上做了很多增强支持很多方面的测试例如JPAMongoDBSpring MVCREST和Redis等。 接下来主要是测试业务逻辑层的代码REST和Mock测试。 以下测试时均不用另外启动应用springboot-2.7.1默认绑定junit5 而不再是junit4后期若想使用 junit4 也可以 Add JUnit4 to classpath若数据库密码配置是密文解密密钥在启动参数里先改回明文再测否则会fail to load ApplicationContext新版本的单测都有对应的专门注解它们内置了启动环境容器。不再需要另外写RunWith(SpringRunner.class) 或SpringBootTest 注解说明
专门的注解提供方说明JPADataJpaTestorg.springframework.boot.test会默认扫描Entity和Repository注解MyBatisMybatisTestcom.baomidou.mybatis-spring-boot-starter-test官方提供支持MyBatis-PlusMybatisPlusTestcom.baomidou.mybatis-plus-boot-starter-test官方提供支持
其它注解说明
AutoConfigureTestDatabase会自动配置一个基于内存的数据库只要依赖中含有一个可用的基于内存的嵌入式数据库。 结果会自动回滚不会发生数据库变化。若使用实际数据库可设置 replace值为Replace.NONERollback是否回滚通过设置 value 为 true or false 决定测试数据是否写入数据库 1.打印版单测
package com.imooc;import org.junit.jupiter.api.Test;/****/
public class LoggerTest {Testpublic void test1() {System.out.println();}
}
2
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Logger2Test {// 选择org.slf4j下的接口private final Logger logger LoggerFactory.getLogger(Logger2Test.class);Testpublic void test1() {logger.info(info...);}
} 2.测试 JPA
版本 junit5 springboot-2.7.2 不启动应用 数据库已配 目录
Repository注解DataJpaTest 注解AutoConfigureTestDatabase注解
依赖
spring-boot-starter-web (2.7.2)
spring-boot-starter-test (with junit5)
mysql-connector-java (8.0)
spring-boot-starter-data-jpa
h2 # 构建基于内存的数据库环境
步骤
DataJpaTest 注解AutoConfigureTestDatabase注解
最终元素已验证
/
Data
Entity // JPA
public class ProductCategory implements Serializable {// ...
}
/
Repository
public interface ProductCategoryRepository extends JpaRepositoryProductCategory, Integer {
}
/
DataJpaTest // 只启动JPA组件不启动全环境
AutoConfigureTestDatabase(replace AutoConfigureTestDatabase.Replace.NONE) // 创建一个基于内存的数据库环境
class ProductCategoryRepositoryTest {AutowiredProductCategoryRepository repository;Testpublic void testFindById() {OptionalProductCategory byId repository.findById(1);System.out.println(查询结果);System.out.println(byId);}
}
控制台
查询结果
Optional[ProductCategory(categoryId1, categoryType1, categoryName热销榜, createTime2022-08-03 06:37:48.0, updateTime2022-08-03 06:37:48.0)]
参考资料
方法讲解https://howtodoinjava.com/spring-boot2/testing/datajpatest-annotation/#1-repository-annotation
实际代码https://github.com/lokeshgupta1981/jakarta-projects/tree/master/spring-boot-projects/hibernate-examples
Auto-configured Data JPA Tests 3.测试 Mybatis
版本 junit5 不启动应用 数据库已配 单测 在主模块里 步骤
MybatisTest注解AutoConfigureTestDatabase注解
依赖
mybatis-spring-boot-starter-testMybatisTest
h2
案例已验证
///子模块
Mapper
public interface LoginMapper {Select(SELECT * FROM t_user)ListUser selectAllUser();
}
///main模块
MybatisTest
AutoConfigureTestDatabase(replace AutoConfigureTestDatabase.Replace.NONE)
class LoginMapperTest {AutowiredLoginMapper loginMapper;Testvoid contextLoads() {ListUser users loginMapper.selectAllUser();System.out.println(查询所有用户);System.out.println(users);}
}
日志结果
查询所有用户
[User(uid1, uname123, upwdpassword, pwdErrnull), User(uid2, unametest, upwdtest, pwdErrnull), User(uid3, uname234, upwdpassword, pwdErrnull), User(uid4, uname234, upwdpassword, pwdErrnull), User(uid5, uname2345, upwdpassword, pwdErrnull), User(uid6, uname452345, upwdpassword, pwdErrnull), User(uid7, uname523, upwdpassword, pwdErrnull), User(uid8, uname453, upwdpassword, pwdErrnull), User(uid9, uname234523, upwdpassword, pwdErrnull), User(uid10, uname45, upwdpassword, pwdErrnull), User(uid11, uname452345, upwdpassword, pwdErrnull), User(uid12, uname2345, upwdpassword, pwdErrnull)] 参考资料
mybatis.orgMyBatisTest的使用 4.测试 MyBatis-Plus
过程和 mb 的差不多它也有另外支持的依赖starter和注解。
引入依赖
com.baomidou.mybatis-plus-boot-starter-test
测试
MybatisPlusTest
AutoConfigureTestDatabase(replace AutoConfigureTestDatabase.Replace.NONE)
Slf4j
class UserMapperTest {AutowiredUserMapper userMapper;Testpublic void testInsert() {User user User.builder().id(51L).mobile(8808820).password(e10adc3949ba59abbe56e057f20f883e).build();int insert userMapper.insert(user);log.info(是否插入成功{}, insert 1);}
}
结果
### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: Column create_time cannot be null
### The error may exist in com/imooc/sso/mapper/UserMapper.java (best guess)
### The error may involve com.imooc.sso.mapper.UserMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO user ( id, mobile, password, create_time, update_time ) VALUES ( ?, ?, ?, ?, ? )
启动成功说明单测步骤是对头的。
参考资料Spring boot Mybatis-Plus数据库单测实战三种方式_CuteXiaoKe的博客-CSDN博客_mybatisplus单元测试 其它默认回滚机制
单测默认会自动回滚如果执行有插入或更新的数据数据库的数据不会变化。且假若新增/更新数据没设置【非null】字段单测也一样会通过可以理解为执行错了随后回滚了
若想写入数据库禁止回滚重写Rollback注解将value值置为false。 DataJpaTest // 只启动JPA组件不启动全环境
AutoConfigureTestDatabase(replace AutoConfigureTestDatabase.Replace.NONE) // 创建一个基于内存的数据库环境
Rollback(value false) // 不自动回滚真实写入数据库
class ProductCategoryRepositoryTest {AutowiredProductCategoryRepository repository;Testpublic void testSave() {ProductCategory category new ProductCategory();System.out.println(插入ing);category.setCategoryId(2);category.setCategoryType(2);category.setCategoryName(女生最爱);category.setCreateTime(new Date());category.setUpdateTime(new Date());repository.save(category);System.out.println(插入一条数据);OptionalProductCategory byId repository.findById(2);System.out.println(插入后查询 byId);}
}
控制台
此时真正读写数据库当插入not null字段时则会触发ConstraintViolationException约束违背异常 否则执行后此时数据库数据写入成功。 附多模块工程的单测
多模块工程
如果只有一个模板以上方式都可以
如果是多模块工程单测建议放 main 模块否则会抛异常
Test ignored.
java.lang.IllegalStateException: Unable to find a SpringBootConfiguration, you need to use ContextConfiguration or SpringBootTest(classes...) with your test· 5.测试Service层
service 层比起 DAO 层要常规得多 测试ServiceImpl
SpringBootTest
class ProductInfoServiceImplTest {AutowiredProductInfoService productInfoService;Testvoid listUpProductInfos() {ListProductInfo upProductInfos productInfoService.listUpProductInfos();System.out.println(验证已上架商品 upProductInfos);}
} 6.Assertions的使用
Assertions是 junit5 代替 junit4 中的 Assert 的一个静态类内置一系列的静态方法提供用户判空判等等方法验证的 api。跳过打印验证执行结构
assertNotNull(obj)
import org.junit.jupiter.api.Assertions;// 判断返回结果是否为空Testvoid findOne() {ProductCategory productCategory productCategoryRepository.findById(3).get();Assertions.assertNotNull(productCategory);}
assertEquals(Expected, Actual)
import org.junit.jupiter.api.Assertions;Testvoid count() {long count productCategoryRepository.count();Assertions.assertEquals(count, 10);} JUnit5Mockito 1. 测试
如果项目组有测试团队最常接触的概念有功能测试、回归测试、冒烟测试等但这些都是由测试人员发起的。
开发人员写的常常是“单元测试”但其实可以细分成 单元测试 和 集成测试 两个。
划分的原因拿常见的 Spring IoC 举例。Spring 不同Bean之间相互依赖例如某API业务逻辑中会依赖不同模块的 ServiceService 方法中又可能依赖不同的 Dao 层方法甚至还会通过 RPC、HTTP 调用外部服务方法。这给我们写测试用例带来了难度本来只想测试某个方法的功能却要考虑一连串的依赖关系。
1.1. 单元测试 单元测试是指对软件中的最小可测试单元进行检查和验证。 通常任何软件都会划分为不同的模块和组件。单独测试一个组件时我们叫做单元测试。单元测试用于验证相关的一小段代码是否正常工作。单元测试不是用于发现应用程序范围内的 bug或者回归测试的 bug而是分别检测每个代码片段。
单元测试不验证应用程序代码是否和外部依赖正常工作。它聚焦与单个组件并且 Mock 所有和它交互的依赖。例如方法中调用发短信的服务以及和数据库的交互我们只需要 Mock 假执行即可毕竟测试的焦点在当前方法上。
单元测试的特点
不依赖任何模块。基于代码的测试不需要在 ApplicationContext 中运行。方法执行快500ms以内也和不启动 Spring 有关。同一单元测试可重复执行N次并每次运行结果相同。
1.2. 集成测试 集成测试在单元测试的基础上将所有模块按照设计要求组装成为子系统或系统进行集成测试。 集成测试主要用于发现用户端到端请求时不同模块交互产生的问题。集成测试范围可以是整个应用程序也可以是一个单独的模块取决于要测试什么。
在集成测试中我们应该聚焦于从控制器层到持久层的完整请求。应用程序应该运行嵌入服务例如Tomcat以创建应用程序上下文和所有 bean。这些 bean 有的可能会被 Mock 覆盖。
集成测试的特点
集成测试的目的是测试不同的模块一共工作能否达到预期。应用程序应该在 ApplicationContext 中运行。Spring boot 提供 SpringBootTest 注解创建运行上下文。使用 TestConfiguration 等配置测试环境。
2. 测试框架
2.1. spring-boot-starter-test
SpringBoot中有关测试的框架主要来源于 spring-boot-starter-test。一旦依赖了spring-boot-starter-test下面这些类库将被一同依赖进去
JUnitjava测试事实上的标准。Spring Test Spring Boot TestSpring的测试支持。AssertJ提供了流式的断言方式。Hamcrest提供了丰富的matcher。Mockitomock框架可以按类型创建mock对象可以根据方法参数指定特定的响应也支持对于mock调用过程的断言。JSONassert为JSON提供了断言功能。JsonPath为JSON提供了XPATH功能。 测试环境自定义Bean TestComponent该注解是另一种Component在语义上用来指定某个Bean是专门用于测试的。该注解适用于测试代码和正式混合在一起时不加载被该注解描述的Bean使用不多。TestConfiguration该注解是另一种TestComponent它用于补充额外的Bean或覆盖已存在的Bean。在不修改正式代码的前提下使配置更加灵活。
2.2. JUnit
前者说了JUnit 是一个Java语言的单元测试框架但同样可用于集成测试。当前最新版本是 JUnit5。
常见区别有
JUnit4 所需 JDK5 版本即可而 JUnit5 需 JDK8 版本因此支持很多 Lambda 方法。JUnit 4将所有内容捆绑到单个jar文件中。Junit 5由3个子项目组成即JUnit PlatformJUnit 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。
JUnit5 和 JUnit4 在注解上的区别在于
功能JUnit4JUnit5声明一种测试方法TestTest在当前类中的所有测试方法之前执行BeforeClassBeforeAll在当前类中的所有测试方法之后执行AfterClassAfterAll在每个测试方法之前执行BeforeBeforeEach在每个测试方法之后执行AfterAfterEach禁用测试方法/类IgnoreDisabled测试工厂进行动态测试NATestFactory嵌套测试NANested标记和过滤CategoryTag注册自定义扩展NAExtendWith RunWith 和 ExtendWith 在 JUnit4 版本在测试类加 SpringBootTest 注解时同样要加上 RunWith(SpringRunner.class)才生效即:
SpringBootTest
RunWith(SpringRunner.class)
class HrServiceTest {
...
}
但在 JUnit5 中官网告知 RunWith 的功能都被 ExtendWith 替代即原 RunWith(SpringRunner.class) 被同功能的 ExtendWith(SpringExtension.class) 替代。但 JUnit5 中 SpringBootTest 注解中已经默认包含了 ExtendWith(SpringExtension.class)。
因此在 JUnit5 中只需要单独使用 SpringBootTest 注解即可。其他需要自定义拓展的再用 ExtendWith不要再用 RunWith 了。
2.3. Mockito
测试驱动的开发TDD要求我们先写单元测试再写实现代码。在写单元测试的过程中我们往往会遇到要测试的类有很多依赖这些依赖的类/对象/资源又有别的依赖从而形成一个大的依赖树。而 Mock 技术的目的和作用是模拟一些在应用中不容易构造或者比较复杂的对象从而把测试与测试边界以外的对象隔离开。
Mock 框架有很多除了传统的 EasyMock、Mockito以外还有PowerMock、JMock、JMockit等。这里选用 Mockito 是因为 Mockito 在社区流行度较高而且是 SpringBoot 默认集成的框架。
Mockito 框架中最核心的两个概念就是 Mock 和 Stub。测试时不是真正的操作外部资源而是通过自定义的代码进行模拟操作。我们可以对任何的依赖进行模拟从而使测试的行为不需要任何准备工作或者不具备任何副作用。
当我们在测试时如果只关心某个操作是否执行过而不关心这个操作的具体行为这种技术称为 mock。比如我们测试的代码会执行发送邮件的操作我们对这个操作进行 mock测试的时候我们只关心是否调用了发送邮件的操作而不关心邮件是否确实发送出去了。
另一种情况当我们关心操作的具体行为或者操作的返回结果的时候我们通过执行预设的操作来代替目标操作或者返回预设的结果作为目标操作的返回结果。这种对操作的模拟行为称为 stub打桩。比如我们测试代码的异常处理机制是否正常我们可以对某处代码进行 stub让它抛出异常。再比如我们测试的代码需要向数据库插入一条数据我们可以对插入数据的代码进行stub让它始终返回1表示数据插入成功。
推荐一个 Mockito 中文文档。 mock 和 spy 的区别 mock方法和spy方法都可以对对象进行mock。但是前者是接管了对象的全部方法而后者只是将有桩实现stubbing的调用进行mock其余方法仍然是实际调用。
如下例因为只mock了List.size()方法。如果mockList是通过mock生成的List的add、get等其他方法都失效返回的数据都为null。但如果是通过spy生成的则验证正常。
平时开发过程中我们通常只需要mock类的某些方法用spy即可。
Testvoid mockAndSpy() {ListString mockList Mockito.mock(List.class);// ListString mockList Mockito.spy(new ArrayList());Mockito.when(mockList.size()).thenReturn(100);mockList.add(A);mockList.add(B);Assertions.assertEquals(A, mockList.get(0));Assertions.assertEquals(100, mockList.size());}
2.4. 插件 Squaretest
强烈推荐这个自动生成单元测试代码的插件像本文说的框架搭配默认的模板就是 JUnit5Mockito.java.ft。
在选中类右键Generate - Generate Test 后不光能生成测试类和方法甚至连Mockito 数据、方法和 Assertions 等都写好了只需要自己改一改即可。
3. 示例
3.1. 单元测试示例
因为 JUnit5、Mockito 都是 spring-boot-starter-test 默认依赖的所以 pom 中无需引入其他特殊依赖。先写个简单的 Service 层方法通过两张表查询数据。 HrService.java
AllArgsConstructor
Service
public class HrService {private final OrmDepartmentDao ormDepartmentDao;private final OrmUserDao ormUserDao;ListOrmUserPO findUserByDeptName(String deptName) {return ormDepartmentDao.findOneByDepartmentName(deptName).map(OrmDepartmentPO::getId).map(ormUserDao::findByDepartmentId).orElse(Collections.emptyList());}
} IDEA 创建测试类 接下来针对该 Service 类创建测试类我们使用的开发工具是 IDEA。点进当前类右键-Go To-Test-Create New Test在 Testing library 中选择 Junit5则在对应目录生成测试类和方法。
HrServiceTest.java
ExtendWith(MockitoExtension.class)
class HrServiceTest {Mockprivate OrmDepartmentDao ormDepartmentDao;Mockprivate OrmUserDao ormUserDao;InjectMocksprivate HrService hrService;DisplayName(根据部门名称查询用户)Testvoid findUserByDeptName() {Long deptId 100L;String deptName 行政部;OrmDepartmentPO ormDepartmentPO new OrmDepartmentPO();ormDepartmentPO.setId(deptId);ormDepartmentPO.setDepartmentName(deptName);OrmUserPO user1 new OrmUserPO();user1.setId(1L);user1.setUsername(001);user1.setDepartmentId(deptId);OrmUserPO user2 new OrmUserPO();user2.setId(2L);user2.setUsername(002);user2.setDepartmentId(deptId);ListOrmUserPO userList new ArrayList();userList.add(user1);userList.add(user2);Mockito.when(ormDepartmentDao.findOneByDepartmentName(deptName)).thenReturn(Optional.ofNullable(ormDepartmentPO).filter(dept - deptName.equals(dept.getDepartmentName())));Mockito.doReturn(userList.stream().filter(user - deptId.equals(user.getDepartmentId())).collect(Collectors.toList())).when(ormUserDao).findByDepartmentId(deptId);ListOrmUserPO result1 hrService.findUserByDeptName(deptName);ListOrmUserPO result2 hrService.findUserByDeptName(deptName error);Assertions.assertEquals(userList, result1);Assertions.assertEquals(Collections.emptyList(), result2);}
因为单元测试不用启动 Spring 容器则无需加 SpringBootTest因为要用到 Mockito只需要自定义拓展 MockitoExtension.class 即可依赖简单运行速度更快。
可以明显看到单元测试写的代码怎么是被测试代码长度的好几倍其实单元测试的代码长度比较固定都是造数据和打桩但如果针对越复杂逻辑的代码写单元测试还是越划算的。
3.2. 集成测试示例
还是那个方法如果使用Spring上下文真实的调用方法依赖可直接用下列方式
SpringBootTest
class HrServiceTest {Autowiredprivate HrService hrService;DisplayName(根据部门名称查询用户)Testvoid findUserByDeptName() {ListOrmUserPO userList hrService.findUserByDeptName(行政部);Assertions.assertTrue(userList.size() 0);}
}
还可以使用MockBean、SpyBean替换Spring上下文中的对应的Bean
SpringBootTest
class HrServiceTest {Autowiredprivate HrService hrService;SpyBeanprivate OrmDepartmentDao ormDepartmentDao;DisplayName(根据部门名称查询用户)Testvoid findUserByDeptName() {String deptName行政部;OrmDepartmentPO ormDepartmentPO new OrmDepartmentPO();ormDepartmentPO.setDepartmentName(deptName);Mockito.when(ormDepartmentDao.findOneByDepartmentName(ArgumentMatchers.anyString())).thenReturn(Optional.of(ormDepartmentPO));ListOrmUserPO userList hrService.findUserByDeptName(deptName);Assertions.assertTrue(userList.size() 0);}
} 小提示SpyBean 和 spring boot data 的问题 当用 SpyBean 添加到 spring data jpa 的dao层上时继承 JpaRepository 的接口会无法启动容器报错 org.springframework.beans.factory.BeanCreationException: Error creating bean with name。包括 mongo 等 spring data 都会有此问题是 spring boot 官方不支持可查看 Issues-7033已在 spring boot 2.5.3 版本修复。 附spring1.x junit4 的测试
Repository接口的Repository注解可写可不写
Test类的必要注解2个
RunWith(SpringRunner.class) // 启动容器环境
SpringBootTest
class ProductCategoryRepositoryTest {
ProductCategoryRepository repository;Testpublic void testFindById() {OptionalProductCategory byId repository.findById(1);System.out.println(查询结果);System.out.println(byId);}
} 其它
IDEA中在目标类内部使用快捷键 [ctrlshiftT] 快速生成测试类有关注解 AutoConfigureTestDatabase
If you prefer your test to run against a real database, you can use the AutoConfigureTestDatabase annotation in the same way as for DataJpaTest. (See Auto-configured Data JPA Tests.) -- Spring Boot Reference Documentation