做网站办贷款上海建设工程招标

当前位置: 首页 > news >正文

做网站办贷款,上海建设工程招标,小说引流推广,国外摄影网站推荐1. 什么是单元测试#xff1f; 对于很多开发人员来说#xff0c;单元测试一定不陌生 单元测试是白盒测试的一种形式#xff0c;它的目标是测试软件的最小单元——函数、方法或类。单元测试的主要目的是验证代码的正确性#xff0c;以确保每个单元按照预期执行。单元测试通…1. 什么是单元测试 对于很多开发人员来说单元测试一定不陌生 单元测试是白盒测试的一种形式它的目标是测试软件的最小单元——函数、方法或类。单元测试的主要目的是验证代码的正确性以确保每个单元按照预期执行。单元测试通常由开发人员来写通过单元测试开发人员可以在代码开发阶段及早发现和修复错误提高代码的质量和可维护性。 1.1 集成测试 ! 单元测试 假如在支付系统有一个Service有个支付预下单的方法逻辑是先根据订单号查询数据库中是否存在支付单再调营销系统接口查询优惠券信息然后根据优惠券信息计算实际支付金额最后再调用支付通道预下单。不用去理解逻辑细节这里的重点是这个方法需要很多外部依赖才能正常执行数据库、中间件、外部系统等等 伪代码如下 Service public class PayService {Autowiredprivate OrderPayRecordMapper orderPayRecordMapper;Autowiredprivate FeishuService feishuService;DubboReferenceprivate MarketingService marketingService;public PrePayResponse prePay(PrePayRequest prePayRequest) {PrePayResponse response PrePayResponse.builder().orderNo(prePayRequest.getOrderNo()).build();// 【查询数据库】校验订单支付记录是否存在OrderPayRecord existedOrderPayRecord orderPayRecordMapper.getByOrderNo(prePayRequest.getOrderNo());if (existedOrderPayRecord ! null !PayStatusEnum.PENDING.equals(existedOrderPayRecord.getStatus())) {throw new BusinessException(5311991, 存在支付中订单请勿重复支付);}// 【调用营销系统】查询优惠信息CouponResponse coupon marketingService.queryCoupon(CouponRequest.builder().orderNo(prePayRequest.getOrderNo()).build()).getData();// 【写数据库】创建订单支付记录OrderPayRecord newOrderPayRecord OrderPayRecord.builder().orderNo(prePayRequest.getOrderNo()).status(PayStatusEnum.PENDING).amount(calcRealAmount(prePayRequest.getAmount(), coupon)).build();orderPayRecordMapper.insert(newOrderPayRecord);// 【调用支付通道】预下单AlipayPrePayResponse alipayPrePayResponse AlipayClient.prePay(AlipayPrePayRequest.builder().orderNo(prePayRequest.getOrderNo()).amount(newOrderPayRecord.getAmount()).build());if (!SUCCESS.equals(alipayPrePayResponse.getResult())) {feishuService.sendMessage(通道预下单失败 orderNo:%s, prePayRequest.getOrderNo());throw new BusinessException(5319997, 通道预下单失败);}response.setPayNo(alipayPrePayResponse.getPayNo());return response;}/*** 计算优惠后的金额*/private Long calcRealAmount(Long originAmount, CouponResponse coupon) {if (coupon ! null coupon.getDiscount() 0 originAmount coupon.getDiscount()) {return NumberUtils.max(0L, originAmount - coupon.getDiscount());}return originAmount;} }针对上面这个支付预下单的方法很多开发人员可能习惯于像下面这样写“单元测试”构造一下入参然后调用被测方法最后打印一下结果 RunWith(SpringRunner.class) SpringBootTest(classes Application.class) public class PayServiceTest {Autowiredprivate PayService payService;Testpublic void test() {PrePayRequest prePayRequest PrePayRequest.builder().orderNo(123).amount(100L).build();PrePayResponse prePayResponse payService.prePay(prePayRequest);System.out.println(prePayResponse);} }但这是单元测试吗 在被测方法中需要查询数据库查询、保存数据需要调用营销系统接口查询优惠券信息需要调用支付通道预下单接口如果内部系统是微服务调用还要起注册中心…… 这个“单元”是不是有点大了 运行单元测试的时候因为会启动整个Spring容器连接配置中心、注册中心连接数据库初始化Redis配置等等所以测试一个方法会很慢很慢还可能因为数据库没连上或者营销系统挂了或者通道接口返回“FAIL”单测运行就直接报错了即使这些所依赖的环境都没问题如果要测试有优惠券的情况还要在营销系统中新增优惠券信息等等。这些限制条件极大影响了测试效率。 真正的单元测试应当独立于外部环境具有隔离性应尽量避免其他类或系统的副作用影响。单元测试的目标是一小段代码例如方法或类应该只关注被测代码的核心逻辑。外部依赖关系那些不容易构造的环境或需要灵活返回预期结果的依赖应从单元测试中移除改为由测试框架创建的 mock 对象来替换依赖对象。一个对象被 mock 后在执行测试时不会调用其真实方法逻辑。例如通过 Mockito 框架 mock 的对象实际上是根据插桩为真实对象创建了一个代理。运行单元测试时调用的是代理对象的方法。 举例来说如果对 OrderPayRecordMapper、MarketingService、AliPayClient 进行 mock那么在执行 payService.prePay() 时执行到这些 mock 对象的方法时并不会真正去操作数据库、通过 RPC 调用远程服务、通过 HTTP 调用第三方通道而是根据插桩返回预期的结果。 通过使用 Mock 对象可以确保测试的独立性、确定性和高效性从而更好地验证代码的正确性和可靠性。Mock 对象不仅提高了测试的执行速度还保证了测试结果的一致性使其能够在各种环境中重复执行。

  1. 为什么要写单元测试 验证代码正确性简便地模拟各种场景。 单元测试能够验证代码的基本功能是否按预期工作。每个小的代码片段如函数或方法的逻辑是否正确无误。程序运行的 bug 往往出现在一些边界条件、异常情况下比如网络超时等在集成环境中模拟这些异常情况都比较困难通过单元测试可以方便地模拟各种情况。 保证重构后代码的正确性。 重构是开发中的家常便饭但每次改动都可能带来未知的问题。很多时候我们不敢修改重构老代码的原因就是因为不知道影响范围担心影响其他逻辑。有了完善的单元测试重构之后运行一下单测就能迅速验证功能是否依旧正常极大降低了引入新bug的风险。 阅读单元测试能帮助我们快速熟悉代码。 良好的单元测试可以作为一个类/方法的“文档”未来开发人员变更通过一个方法的单元测试可以知道指定输入对应的预期输出是什么不需要深入的阅读代码便能知道这个方法大概实现了什么功能有哪些特殊情况需要考虑等等。 单元测试成本很低有利于集成测试进行提高效率。 编写单元测试虽然会花费大量精力但是一旦完成了单元测试的工作很多基础的bug将会被发现并且修复这些bug的成本很低比如开发阶段在本地及时发现、修复这些bug不用等部署到dev/test等环境运行时遇到某个bug还得在本地修改再重新部署到dev/test环境复测…… 。 经过单元测试的对象接口、函数等可靠性会得到保证在将来的系统集成中可以极大减少在一些简单的bug上花费的时间比如空指针异常、数组下标越界、代码执行分支和预期不符等从而可以把精力放在系统交互和全局的功能实现相关的测试上。 Capers Jones 在《Applied Software Measurement : Global Analysis of Productivity and Quality》中有一张图比较形象地描述了在软件生命周期中bug产生的概率、bug被发现的概率、bug被修复的成本之间的关系 从这个图中可以发现bug发现的越晚修复它的成本就越高。在开发阶段是产生bug概率最高的时候在开发阶段也是修复bug成本最低的时候如果暂时抛开TDD不谈单元测试是性价比最高的测试。
  2. 编写单元测试 3.1 单元测试的范围是什么 一般推荐优先对核心业务逻辑代码、有复杂计算比如金融、支付业务比较重要的计算、复用性代码如比较重要的工具类等进行单元测试。 3.2 什么时候编写单元测试 在编写代码之前 测试驱动开发TDDTest-Driven Development是一种开发方法要求在编写实际代码之前先编写单元测试优点是可以确保每一行代码都有相应的测试覆盖从而提高代码质量和稳定性。 在实现功能代码的同时 如果没有采用 TDD 方法可以在实现功能代码的同时编写单元测试。这种方法可以在开发过程中及时发现和修复代码中的问题。 在修复 bug 之前 在修复 bug 之前先编写一个能重现该 bug 的单元测试然后修复代码使该测试通过。这可以确保 bug 被修复并且防止以后再出现同样的问题。 在重构代码之前 在重构代码之前先编写单元测试来验证当前代码的行为然后进行重构。这样可以确保重构不会引入新的错误并且功能保持不变。 在添加新功能之前 在添加新功能之前编写单元测试可以确保新功能的正确性并且不会破坏现有功能。
    3.3 通过JUnit和Mockito编写单元测试 JUnit是Java中最流行的测试框架目前主流的Mock工具有Mockito、Spock、JMockit、PowerMock、EasyMock等 Mockito的语法简介易上手使用者众多因此我们选择使用JUnit来写单元测试使用Mockito来mock对象。 一般写单元测试的步骤为构造被测方法入参 - 对依赖插桩 - 执行被测方法 - 断言 JUnit基础用法 maven依赖 dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/versionscopetest/scope /dependency用一个Test注解就能定义一个方法为测试方法用Assert进行断言 import org.junit.Assert; import org.junit.Test;public class JUnitTest {Testpublic void testFact() {int calcResultMath.addExact(1,1);Assert.assertEquals(2, calcResult);} }JUnit大家很熟悉这里不再赘述。更多JUnit的使用比如Rule、TimeoutJUnit5中的参数化测试等等可以参考官方文档文档中有很多Demo供参考 JUnit4 https://junit.org/junit4/ 或者 https://github.com/junit-team/junit4/wiki/ JUnit5 https://junit.org/junit5/docs/current/user-guide/ Mockito基础用法 这里只列举一下Mockito常见的用法在项目中有其他场景可以参考Mockito官方文档https://javadoc.io/static/org.mockito/mockito-core/4.5.1/org/mockito/Mockito.html maven依赖spring-boot-starter-test已经包含mockito-core如果已经引了spring-boot-starter-test就不需要再引mockito-core了另外mockito-inline是在mock静态方法的时候需要使用 dependencygroupIdorg.mockito/groupIdartifactIdmockito-core/artifactIdversion4.5.1/versionscopetest/scope /dependency dependencygroupIdorg.mockito/groupIdartifactIdmockito-inline/artifactIdversion4.5.1/versionscopetest/scope /dependency1Mockito中的mock对象 在 Mockito 中主要有三种对象类型分别由 InjectMocks、Mock 和 Spy 注解来定义 InjectMocks用于被测试的类。Mockito 会通过反射创建这个类的实例类似于 Spring 容器为 Component 修饰的类创建实例。如果该实例有依赖Mockito 会自动将标记为 Mock 或 Spy 的对象注入到这个实例中。单元测试执行时会真正执行这个实例的方法。Mock用于需要被 mock 的依赖类或接口。Mockito 会通过字节码生成框架ByteBuddy为其创建代理对象。单元测试执行时不会调用真正的方法而是根据插桩返回预期的结果。Spy用于部分模拟的对象。Spy 对象既可以调用真实对象的方法也可以模拟其行为。默认情况下调用的是实际对象的方法当对其插桩后调用的是模拟后的行为。 简而言之用 InjectMocks 来修饰被测试的类只能是类不能是接口用 Mock 或 Spy 来修饰需要 mock 的对象类或接口都行。 2写单元测试时就不能用 RunWith(SpringRunner.class) 和 SpringBootTest(classes Application.class) 了因为我们不需要真正初始化所依赖的对象也就不需要加载Spring应用上下文。 在单元测试类上添加RunWith(MockitoJUnitRunner.class)注解用来初始化Mockito自动注入mock对象等比如要对上面PayService类写单元测试 RunWith(MockitoJUnitRunner.class) public class PayServiceTest {InjectMocksprivate PayService payService;Mockprivate OrderPayRecordMapper orderPayRecordMapper;Mockprivate MarketingService marketingService;Testpublic void testPrePaySuccess() {// 单元测试内容} } 3对mock对象的非静态方法插桩比如 假设当通过orderPayRecordMapper.getByOrderNo(String orderNo)查询订单号为80984234938472的支付订单时返回null 假设当通过marketingService.queryCoupon(CouponRequest request)查询优惠券时返回null 可以这样写 RunWith(MockitoJUnitRunner.class) public class PayServiceTest {InjectMocksprivate PayService payService;Mockprivate OrderPayRecordMapper orderPayRecordMapper;Mockprivate MarketingService marketingService;Testpublic void testPrePaySuccess() {PrePayRequest prePayRequest PrePayRequest.builder().orderNo(80984234938472).amount(100L).build();// 插桩 假设是第一次下单数据库中还没有相同订单号的支付记录(执行到orderPayRecordMapper.getByOrderNo时不会真正查数据库会直接返回null)Mockito.when(orderPayRecordMapper.getByOrderNo(prePayRequest.getOrderNo())).thenReturn(null);// 插桩 假设没有优惠券(执行到marketingService.queryCoupon时不会真正调营销系统接口会直接返回null)Mockito.when(marketingService.queryCoupon(Mockito.any())).thenReturn(Response.buildSuccess(null));// 执行被测方法PrePayResponse prePayResponse payService.prePay(prePayRequest);// 断言Assert.assertNotNull(prePayResponse);Assert.assertNotNull(prePayResponse.getPayNo());} }4参数匹配上面在对orderPayRecordMapper.getByOrderNo()进行插桩时方法入参可以传真实的也可以传任意值比如对marketingService.queryCoupon()进行插桩时方法入参传的Mockito.any()表示参数为任意值的时候都返回thenReturn()指定的结果。此外还有Mockito.any(Class type)、Mockito.anyString()、Mockito.anyLong()…… 5上面例子中支付通道预下单接口是通过一个静态方法AlipayClient.prePay()来调用的Mockito3.4.0之后支持对静态方法打桩需要依赖mockito-inline Test public void testPrePaySuccess() {PrePayRequest prePayRequest PrePayRequest.builder().orderNo(80984234938472).amount(100L).build();Mockito.when(orderPayRecordMapper.getByOrderNo(prePayRequest.getOrderNo())).thenReturn(null);Mockito.when(marketingService.queryCoupon(Mockito.any())).thenReturn(Response.buildSuccess(null));// 插桩 假设调用支付通道预下单接口返回成功MockedStaticAlipayClient alipayClientMockedStatic Mockito.mockStatic(AlipayClient.class);alipayClientMockedStatic.when(() - AlipayClient.prePay(Mockito.any())).thenReturn(AlipayPrePayResponse.builder().payNo(123).result(SUCCESS).build());// 执行被测方法PrePayResponse prePayResponse payService.prePay(prePayRequest);// 断言Assert.assertNotNull(prePayResponse);Assert.assertNotNull(prePayResponse.getPayNo());// 注意mock的静态对象使用完毕要调用close()来释放或者用try-with-resources方式alipayClientMockedStatic.close(); }注意为了保证测试隔离性、避免内存泄漏mock的静态对象使用完毕要调用close()来释放或者用try-with-resources方式来释放也可以在Before中初始化JUnit5中是BeforeEach在After中释放JUnit5中是AfterEach private MockedStaticAlipayClient alipayClientMockedStatic;Before public void setUp() {alipayClientMockedStatic Mockito.mockStatic(AlipayClient.class); }After public void tearDown() {alipayClientMockedStatic.close(); }Test public void testPrePaySuccess() {PrePayRequest prePayRequest PrePayRequest.builder().orderNo(80984234938472).amount(100L).build();Mockito.when(orderPayRecordMapper.getByOrderNo(prePayRequest.getOrderNo())).thenReturn(null);Mockito.when(marketingService.queryCoupon(Mockito.any())).thenReturn(Response.buildSuccess(null));// 插桩 假设调用支付通道预下单接口返回成功alipayClientMockedStatic.when(() - AlipayClient.prePay(Mockito.any())).thenReturn(AlipayPrePayResponse.builder().payNo(123).result(SUCCESS).build());// 执行被测方法PrePayResponse prePayResponse payService.prePay(prePayRequest);// 断言Assert.assertNotNull(prePayResponse);Assert.assertNotNull(prePayResponse.getPayNo()); }6验证方法执行对于一些有返回值的方法可以通过断言来进行预期判断对于一些没有返回值的void方法可以通过verify来验证这个方法是否执行成功比如在上面例子中验证feishuService.sendMessage()这个方法是否被成功执行 Mockito.verify(feishuService).sendMessage(Mockito.any()); // 验证feishuService.sendMessage()方法成功执行了1次 Mockito.verify(feishuService,Mockito.times(2)).sendMessage(Mockito.any()); // 验证feishuService.sendMessage()方法成功执行了2次7异常断言当预期某个分支会抛异常时可以通过如下方式 ① 通过自定义方式 Test public void testPrePayTimeout{try {payService.prePay();Assert.fail();} catch (Exception e) {Assert.assertTrue(e instanceof TimeoutException);Assert.assertEquals(超时啦,e.getMessage());} }② 通过Mockito的方式当判定某个分支是否抛异常时可以通过Rule来定义异常断言比如 Rule public ExpectedException thrown ExpectedException.none(); Test public void testPrePayTimeout{thrown.expect(TimeoutException.class); // 当执行payService.prepare()时预期抛出TimeoutException异常thrown.expectMessage(超时啦); // 当执行payService.prepare()时预期抛出异常message是超时啦payService.prePay(); }③ 通过JUnit的方式如果只对指定的异常类做断言JUnit中还有一个比较简单的方式直接在Test注解上定义预期的异常 Test(expected TimeoutException.class) public void testPrePayTimeout{payService.prePay(); }8private方法如何测试一般private方法不建议进行单元测试可以在测public方法的时候来测。当然也可以通过spring-test测试私有方法通过ReflectionTestUtils.invokeMethod调用被测方法 PrePayResponse prePayResponse ReflectionTestUtils.invokeMethod(payService, prePay, prePayRequest);3.4 人工写单元测试太累要学会站在巨人的肩膀上 一个项目中能坚持写单元测试是一件很不容易的事情可能开发人员没有写单元测试的习惯或者由于赶业务而没有时间去写或者是在项目后期为代码编写单元测试工作量巨大觉得编写单元测试浪费时间总之有很多理由导致坚持不下去。 所以可以借助一些工具来为我们自动生成单元测试比如Idea中有一些专门用来生成单元测试的插件比如TestMe、Squaretest、JCode5等也可以利用AI插件比如通义灵码来生成单元测试。具体用什么哪个好用看个人习惯。不够有些工具自动生成的单元测试可能参数什么的不符合要求或者运行不通过需要重新调整一下才可以。
  3. 单元测试覆盖率检测 测试的时候我们常常关心是否所有代码都测试到了这个指标就叫做“代码覆盖率”code coverage代码覆盖率是一个非常重要的质量指标。它可以帮助我们了解代码中哪些部分被测试覆盖哪些部分可能存在风险。通常我们关注的覆盖率有几个测量维度 类覆盖率测试用例覆盖的类的百分比。方法覆盖率测试用例覆盖的方法的百分比。行覆盖率测试用例执行的代码行数占总行数的百分比。分支覆盖率代码中每个条件分支如 if-else 语句被测试用例执行的情况。指令覆盖率测试用例执行的字节码指令占总指令数的百分比。 jacoco是一款比较强大的单测覆盖率检测工具Idea中已经集成了Jacoco单元测试覆盖率检测也可以通过它的maven插件来检测在Jenkins等持续集成平台上打包部署的时候也可以进行检测原理也是执行maven插件。 4.1 通过Idea中集成的jacoco检测单元测试覆盖率 在Idea右上Configuration - Edit Modify options - Specify alternative coverage runner 然后在Code Coverage那就能选择JaCoCo了默认是Idea 配置好之后运行覆盖率检测 就能检测当前单元测试对被测代码的覆盖率了在右边栏Coverage里就是单元测试覆盖率结果有类覆盖率、方法覆盖率、行覆盖率、分支覆盖率双击类名可以看到代码左边有不同颜色的标识默认绿色表示完全覆盖黄色表示部分覆盖红色表示未覆盖 4.2 使用jacoco的maven插件进行单测覆盖率检测 如果是简单的maven项目直接在pom文件中添加下面两个插件 plugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-surefire-plugin/artifactIdversion2.18.1/versionconfigurationskipTestsfalse/skipTeststestFailureIgnoretrue/testFailureIgnoreargLine${jacocoArgLine}/argLine/configuration /plugin plugingroupIdorg.jacoco/groupIdartifactIdjacoco-maven-plugin/artifactIdversion0.8.6/versionexecutionsexecutiongoalsgoalprepare-agent/goal/goalsconfigurationpropertyNamejacocoArgLine/propertyName/configuration/executionexecutionidreport/idphasetest/phasegoalsgoalreport/goal/goals/execution/executions /plugin如果是maven父子项目可以在父项目添加上面两个插件可以检测所有子项目代码的覆盖率jacoco只能针对每个maven子项目生成单独的覆盖率报告如果想要把生成的报告聚合在一起可以找一个maven子模块来做报告聚合比如我们可以让-starter子项目来做报告聚合需要保证两点1 做报告聚合的模块需要添加对应报告模块的maven依赖2 在-starter子项目的pom文件中添加如下插件还可以通过exclude标签来禁止对某个包、类等生成单测覆盖率 plugingroupIdorg.jacoco/groupIdartifactIdjacoco-maven-plugin/artifactIdversion0.8.6/versionconfigurationexcludesexclude/com/danny/test/mapper//exclude/excludes/configurationexecutionsexecutionidmy-report/idphasetest/phasegoalsgoalreport-aggregate/goal/goalsconfigurationexcludesexclude/exclude/excludes/configuration/execution/executions /plugin写完单元测试执行 mvn clean test 后maven单模块项目会在 target\site\jacoco、聚合项目会在 target\site\jacoco-aggregate 目录生成单元测试覆盖率报告打开index.html就可以看到整个项目、某个包、类的单测覆盖率 每个指标的含义 InstructionsJava 字节指令的覆盖率Branches分支覆盖率Cxty(Cyclomatic Complexity)圈复杂度Jacoco 会为每一个非抽象方法计算圈复杂度圈复杂度的值表示在一个方法里面所有可能路径的最小数目简单的说就是为了覆盖所有路径所需要执行单元测试数量圈复杂度大说明程序代码可能质量低且难于测试和维护。Lines: 行覆盖率只要本行有一条指令被执行则本行则被标记为被执行。Methods: 方法覆盖率任何非抽象的方法只要有一条指令被执行则该方法被计为被执行。Classes: 类覆盖率所有类包括接口只要其中有一个方法被执行则标记为被执行构造函数和静态初始化块也算作方法。 点进去某个被检测的项目 - 包 - 类可以看到代码中具体哪个方法、哪一行没有覆盖 在最左边可以看到有不同颜色红、黄、绿的小钻石每行代码还可能有不同颜色红、黄、绿的背景。 其中钻石代表分支覆盖情况 红色钻石当前行所有的分支都没有被覆盖 黄色钻石当前行只有部分分支被覆盖鼠标放上去可以查看详情 绿色钻石当前行所有分支都被覆盖
    背景代表指令覆盖情况 红色背景当前行没有任何指令被执行 黄色背景当前行只有部分指令被执行这里解释下 绿色背景当前行所有指令都被执行
    通过单元测试覆盖率能够清晰地了解到某个类、某个方法、某行代码、某个分支等是否被覆盖从而能够促使开发人员更高效地完善单元测试。 那单元测试覆盖率达到多少才算合理呢答案是并没有明确的要求70%-80%的覆盖率已经足够优秀能够有效发现和避免大多数问题。不需要一味追求100%的覆盖率。 然而部分公司团队可能是为了保证代码的严谨性或者是“领导要求”对单元测试覆盖率要求很高甚至要求达到100%这种做法看似合理但实际上并不可取原因有 边际效应递减在覆盖率达到一定水平后继续增加覆盖率的边际效应会递减。换句话说达到80%的覆盖率和达到100%的覆盖率所付出的努力和资源差别巨大而带来的质量提升却有限。 实际价值有限为了达到100%的覆盖率开发人员可能会编写大量低质量、仅为了覆盖率的测试。这些测试不仅无法提高代码质量还可能增加维护负担降低开发效率。 时间和成本编写和维护高覆盖率的单元测试需要大量的时间和成本。在实际项目中需要权衡项目进度和代码质量合理分配资源而不是一味追求高覆盖率。
    最后单元测试覆盖率只能代表你测试过哪些代码不能代表你是否测试好这些代码不能盲目追求代码覆盖率而应该想办法设计更有效的案单测用例 转载请注明出处《单元测试实施最佳方案背景、实施、覆盖率统计 》 https://blog.csdn.net/huyuyang6688/article/details/140397135