中国建设银行网站医保,对口网站怎么做,做一个php连接sql网站,苏州网站建设名字一、做好单测#xff0c;慢即是快
对于单元测试的看法#xff0c;业界同仁理解多有不同#xff0c;尤其是在业务变化快速的互联网行业#xff0c;通常的问题主要有#xff0c;必须要做吗#xff1f;做到多少合适#xff1f;现在没做不也挺好的吗#xff1f;甚至一些大…一、做好单测慢即是快
对于单元测试的看法业界同仁理解多有不同尤其是在业务变化快速的互联网行业通常的问题主要有必须要做吗做到多少合适现在没做不也挺好的吗甚至一些大佬们也是存在不同的看法。我们如下先看一组数字
“在 STICKYMINDS 网站上的一篇名为 《 The Shift-Left Approach to Software Testing 》 的文章中提到假如在编码阶段发现的缺陷只需要 1 分钟就能解决那么单元测试阶段需要 4 分钟功能测试阶段需要 10 分钟系统测试阶段需要 40 分钟而到了发布之后可能就需要 640 分钟来修复。”——来自知乎网站节选 对于这些数字的准确性我们暂且持保留意见。大家可以想想我们实际中遇到的线上问题大概需要消耗多少工时除了要快速找到bug修复bug上线还要修复因为bug引发的数据问题最后还要复盘看后续如何能避免线上问题这样下来保守估计应该不止几人日吧。所以这篇文章作者所做的调研数据可信度还是很高的
缺陷发现越到交付流程的后端其修复成本就越高。
有人说写单测太耗费时间了会延长交付时间其实不然
1研测同学大量的往返交互比编写单测的时间要长的多集成测试的时间被拖长。
2没经过单测的代码bug会多开发同学忙于修复各种bug对代码debug跟踪调试找问题也要消耗很多精力。
3后期的线上问题也会需要大量的精力去弥补。
如果有了单元测试的代码且能实现一个较高的行覆盖率则可以将问题尽可能消灭在开发阶段。同时有了单测代码的积累每次代码改动后可以提前发现这次改动引发的其他关联问题上线也更加放心。单测虽然使提测变慢了一些软件质量更加有保障从而节省了后续同学的精力从整体看其实效率更高。
所以做好单测慢即是快。
做为一名开发者我们需要对自己的代码质量负责也更能体现我们开发者的工匠精神。
二、编写单元测试
Junit5使用
maven依赖
!-- Springboot提供的单测框架,提供一些单测工具支持,默认支持Mockito、junit5 --
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdversion2.5.4/version
/dependency!-- 或单独引入 --
dependencygroupIdorg.junit.jupiter/groupIdartifactIdjunit-jupiter/artifactIdversion5.7.2/versionscopecompile/scope
/dependency
dependencygroupIdorg.mockito/groupIdartifactIdmockito-core/artifactIdversion3.9.0/versionscopecompile/scope
/dependency
dependencygroupIdorg.mockito/groupIdartifactIdmockito-junit-jupiter/artifactIdversion3.9.0/versionscopecompile/scope
/dependencyJuint常用注解 单类示例
通过idea的Squaretest插件直接生成的测试类如下
ExtendWith(MockitoExtension.class)
public class MockUserServiceTest {Mockprivate UserManager mockUserManager;InjectMocksprivate MockUserService mockUserService;BeforeEachpublic void setUp() {mockUserService new MockUserService(mockUserManager);}Testpublic void testGetUserByAge() {// Setupwhen(mockUserManager.findByAge(0)).thenReturn(Arrays.asList(new User(0, name, 0),new User(1, name, 0)));// Run the testfinal ListUser result mockUserService.getUserByAge(0);// Verify the results}Testpublic void testGetUserByAge_UserManagerReturnsNoItems() {// Setupwhen(mockUserManager.findByAge(0)).thenReturn(Arrays.asList(new User(0, name, 0), new User(1, name, 1)));// Run the testfinal ListUser result mockUserService.getUserByAge(0);// Verify the resultsassertThat(result).isEqualTo(Collections.emptyList());}
}
需注意Junit5.x 与Junit4.x 生成的测试类中Junit4的测试类和测试方法必须要public关键字修改。
因为JUnit 4.x使用Java反射机制来查找和运行测试而Java反射要求被访问的类和方法必须是public的。
JUnit 5.x也称为Jupiter在设计和实现上更加现代化它引入了一些新的特性和改进包括更灵活的测试发现机制。在JUnit 5.x中测试类和测试方法的访问修饰符要求更加宽松。
将测试方法和类声明为public也有助于确保它们能够被其他测试框架或工具如Maven、Gradle、IDE等正确地发现和运行。因此在编写JUnit测试时即使JUnit 5.x允许更宽松的访问修饰符但将测试类和测试方法声明为public仍然是一个好习惯。
springboot集成测试
springboot集成测试旨在验证Spring Boot应用程序的各个组件之间的交互和整体行为。集成测试非常重要因为它可以帮助开发人员确保应用程序在不同的环境中都能正常运行。通过集成测试可以检测应用程序中的潜在问题提高代码的可靠性和稳定性。
Mockito常用注解
MockBean 用于在 Spring Boot 测试环境中创建并注入一个 mock 的 bean。 用途用于在 Spring Boot 测试环境中创建一个 mock 的 bean并将其注入到 Spring 应用程序上下文中。 特点 适用于集成测试特别是在使用 SpringBootTest 注解的测试类中。 替换掉 Spring 容器中已有的 bean或者添加一个新的 mock bean。 可以在测试类中直接使用 Autowired 注解来注入这个 mock bean。
Mock 用于创建一个 mock 对象但不将其注入到 Spring 应用程序上下文中。 用途用于创建一个 mock 对象但不将其注入到 Spring 应用程序上下文中。 特点 适用于单元测试特别是在不需要 Spring 上下文的测试中。 需要手动注入到测试类或方法中。 通常与 InjectMocks 一起使用以便将 mock 对象注入到被测试的类中。
Spy 用于创建一个部分 mock 对象即一个真实的对象但可以对其中的某些方法进行 mock。 用途用于创建一个部分 mock 对象即一个真实的对象但可以对其中的某些方法进行 mock。 特点 适用于需要调用真实对象的方法同时对某些方法进行 mock 的场景。 可以使用 doReturn(...).when(...) 或 when(...).thenReturn(...) 来模拟方法的行为。
InjectMocks 用于创建一个被测试的类的实例并将带有 Mock 或 Spy 注解的 mock 对象注入到该实例中。 用途用于创建一个被测试的类的实例并将带有 Mock 或 Spy 注解的 mock 对象注入到该实例中。 特点 适用于需要将 mock 对象注入到被测试的类中的场景。 自动将 mock 对象注入到被测试类的构造函数、字段或 setter 方法中。
集成示例
SpringBootTest(webEnvironment SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MockInjectServiceImplTest{ /*** 通过MockBean的方式创建一个Mock的MockRpcService的Bean* 并将其注入到spring的上下文中*/MockBeanprivate MockRpcService mockRpcService;Resourceprivate MockInjectService mockInjectService;BeforeEachvoid setUp() {MockitoAnnotations.openMocks(this);ReflectionTestUtils.setField(mockInjectService, systemEnv, {\key\, \value\});when(mockRpcService.queryCardNo(anyString())).thenReturn(cardNo);/*** 1、when(...).thenReturn(...) 语法* 这种语法在某些情况下可能会被 Mockito 误认为是在调用 toString 方法特别是当 mockRpcService 对象的 toString 方法被重写时。* 若在使用 Mockito 模拟这个接口时遇到了 WrongTypeOfReturnValue 异常这通常意味着 Mockito 误认为你在调用 toString 方法而不是 queryMockResp 方法* 如果 mockRpcService 的 toString 方法返回 MockResp 类型那么 Mockito 会抛出 WrongTypeOfReturnValue 异常。** 2、doReturn(...).when(...) 语法* 这种语法更加明确直接指定了方法的返回值避免了类型不匹配的问题。适用于所有需要模拟方法返回值的场景。* 为了确保代码的健壮性和可读性建议使用 doReturn(...).when(...) 语法。**** 下面的例子 使用when(...).thenReturn(...)时 抛出了org.mockito.exceptions.misusing.WrongTypeOfReturnValue:* MockResp cannot be returned by toString() toString() should return String* 这样的异常。*///when(mockRpcService.queryMockResp(any(MockReq.class))).thenReturn(MockRespReflection.getMockResp());doReturn(MockRespReflection.getMockResp()).when(mockRpcService).queryMockResp(any(MockReq.class));doReturn(MockRespReflection.getMockRespList()).when(mockRpcService).getMockRespList(anyInt());}Testpublic void testGeneralDeal(){// 执行被测方法MockReq mockReqInput1 new MockReq();mockReqInput1.setName(True-Person);MockResp mockRespResult mockInjectService.generalDeal(mockReqInput1);log.info(mockResp:{}, JSON.toJSONString(mockRespResult));// 结果比对断言Assert.assertNotNull(mockRespResult);}Testpublic void testInjectDeal() {// 执行被测方法MockReq mockReqInput1 new MockReq();mockReqInput1.setName(True-Person);MockResp mockRespResult mockInjectService.injectDeal(mockReqInput1);// 结果比对断言Assert.assertNotNull(mockRespResult);}Testpublic void testBeautifulDeal() {// Setupfinal MockResp mockResp new MockResp(cardNo, 0, false);// Run the testfinal String result mockInjectService.beautifulDeal(mockResp);// Verify the resultsassertThat(result).isEqualTo(result);}Testpublic void testVoidDeal() {// Setupfinal MockReq req new MockReq();req.setName(name);// Run the testmockInjectService.voidDeal(req);}
}
以上示例通过MockBean创建一个Rpc服务MockRpcService的mock实例可以对接口的相关方法通过when(...).thenReturn(...) 或doReturn(...).when(...)语法mock。
需注意when(...).thenReturn(...)语法在某些情况下可能会被 Mockito 误认为是在调用 toString 方法特别是当 mockRpcService 对象的 toString 方法被重写时。
而doReturn(...).when(...) 语法更加明确直接指定了方法的返回值避免了类型不匹配的问题。适用于所有需要模拟方法返回值的场景。建议使用 doReturn(...).when(...) 语法
RPC接口MockRpcService
*** Mockito框架研发场景-RPC接口*/
public interface MockRpcService {String queryCardNo(String name);MockResp queryMockResp(MockReq req);public ListMockResp getMockRespList(Integer age);
通过MockRespReflection类中的静态方法 对RPC接口的方法数据进行mock可以采用直接字符串、文件等形式提前准备数据这里采用读取文件形式进行mock
ublic class MockRespReflection {public static MockResp getMockResp() {try {String json new String(Files.readAllBytes(Paths.get(src/test/file/xxx.json)));return JSON.parseObject(json, new TypeReferenceMockResp(){});} catch (IOException e) {throw new RuntimeException(e);}}/*** 从指定的JSON文件中读取并解析MockResp对象列表** return 解析后的MockResp对象列表* throws RuntimeException 如果读取文件时发生IO异常将其包装成RuntimeException抛出*/public static ListMockResp getMockRespList() {try {// 读取JSON文件内容并解析为MockResp对象列表String json new String(Files.readAllBytes(Paths.get(src/test/file/mockRespList.json)));return JSON.parseObject(json, new TypeReferenceListMockResp(){});} catch (IOException e) {// 捕获IO异常并将其包装成RuntimeException抛出throw new RuntimeException(e);}}
}
通过以上配置就可以进行springboot流程的集成测试。Spring Boot集成测试是确保应用程序正确性和可靠性的重要手段。通过上述实践可以有效地进行集成测试并提高代码质量。
参考
一台不容错过的Java单元测试代码“永动机”-CSDN博客