程序员,为什么老是你的bug最多单元测试

北京中科医院几级 https://wapyyk.39.net/hospital/89ac7_guides.html

本篇文章是SpringBoot单元测试系列的第四篇,主要汇总下平时在日常开发中我们都要面对的场景,针对每一种场景,提出了针对的解决方案。更多解决方案还请大家继续补充。

一、测试成本

1.1灵*深处的拷问

你的代码质量如何度量?你是如何保证代码质量?你敢随时重构代码吗?你是如何确保重构的代码依然保持正确性?你是否有足够信心在没有测试的情况下随时发布你的代码?

如果答案都比较犹豫,那么就证明我们非常需要单元测试。(ps:不会有人心里想的是我们需要测试同学吧)

Web应用中的单元测试更加重要,在Web产品快速迭代的时期,每个测试用例都给应用的稳定性提供了一层保障。API升级,测试用例可以很好地检查代码是否向下兼容。对于各种可能的输入,一旦测试覆盖,都能明确它的输出。代码改动后,可以通过测试结果判断代码的改动是否影响已确定的结果。

所以,应用的Controller、Service、Common、Manager等代码,都必须有对应的单元测试保证代码质量。当然,框架和插件的每个功能改动和重构都需要有相应的单元测试,并且要求尽量做到修改的代码能被%覆盖到。

特别是中大型项目,经过多年的代码迭代,业务逻辑复杂,代码改动很容易牵一发动全身,单元测试就能给应用的稳定性提供了一层保障。不用面对qa的灵*拷问:

为什么老是你的bug最多!

1.2单测是手段不是目的

单测行覆盖率高不代表应用的质量就一定高,但是单测行覆盖率低一定代表着这个应用出现质量问题的可能性就越大

还是引用前面话,我们不要为了单测而写单测,如果是把单测当做是目的来做,那么就偏离了单测的意义,自然而然你就认为这个单测的成本是高的。.....................你品你细品

1.3软件的质量不是测试出来的,而是设计和维护出来的

作为底层开发人员,我们清晰每一行代码,也就是最小执行单元。哪里容易出现错误,这次改造涉及到了那些代码。一线开发人员一定是最清楚的人,没有之一。

从这个角度来看,这就不是成本的问题了,是职责范围内的事情。

如果单元测试都不做,就好比我去街上买鸡蛋,我问老板鸡蛋是好是坏,老板说我不知道,然后说坏了拿来可以换。你认为那个成本更大呢?那个代价更大呢?玩意坏鸡蛋把人吃坏了,这老板岂不是赔了夫人又折兵,还要受到法律的制裁?/

二、启动缓慢

2.1SpringBoot2.2+解决方案

spring.main.lazy-initialization=true

SpringApplication会自动添加一个叫LazyInitializationBeanFactoryPostProcessor的处理器

2.2SpringBoot之前的版本

给应用上下文提前装载一个类似的处理器,然后通过BeanFactoryPostProcessor在容器刷新前循环将BeanDefinition声明懒加载

publicclassBeanLazyApplicationContextInitializerimplementsApplicationContextInitializerConfigurableApplicationContext{

Overridepublicvoidinitialize(ConfigurableApplicationContextapplicationContext){applicationContext.addBeanFactoryPostProcessor(newLazyBeanDefinitionPostProcessor());}publicstaticclassLazyBeanDefinitionPostProcessorimplementsBeanFactoryPostProcessor,Orded{

OverridepublicvoidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)throwsBeansException{for(StringbeanName:beanFactory.getBeanDefinitionNames()){BeanDefinitionbeanDefinition=beanFactory.getBeanDefinition(beanName);if(beanDefinitioninstanceofAbstractBeanDefinition){beanDefinition.setLazyInit(true);}}}

OverridepublicintgetOrder(){turnOrded.HIGHEST_PRECEDENCE;}}}

Slf4j

ActiveProfiles({"local"})

ContextConfiguration(initializers={BeanLazyApplicationContextInitializer.class})//使用Spring容器引导

RunWith(SpringRunner.class)

SpringBootTest(classes={CenterProviderApplication.class})//指定启动类publicclassBaseApplicationTest{}

2.3如何确定版本?

Idea中搜索

SpringApplication查看所在的包

三、数据隔离

3.1解决方案

下面这两个方法

第一个方法因为是使用测试全局事务配置,所以默认是回滚。第二个方法因为使用了方法事务配置,所以会忽略全局配置,然后提交事务。

Slf4j

ActiveProfiles({"local"})//使用Spring容器引导

RunWith(SpringRunner.class)//默认就是回滚,不用加

Rollback,如果全局不想回滚就在这个吧

Rollback(false),如果某个单测不想回滚,就放到单侧类上

Transactional

SpringBootTest(classes={CenterProviderApplication.class})//指定启动类publicclassBaseApplicationTest{//全局事务,默认自动回滚

TestpublicvoidtestInsert(){Stringjson="{\n"+"\"id\":,\n"+"\"arrivalOrderId\":,\n"+"\"goodsDeployId\":,\n"+"\"expectedReceiveNum\":,\n"+"}";OrderDetailDOorderDetail=TestConsole.toObject(json,ArrivalNoticeOrderDetailDO.class);orderDetail.setId(null);orderDetail.setCaterId(L);//trueAssert.assertTrue(DB.insert(orderDetail)0);//notNullAssert.assertNotNull(DB.selectById(orderDetail.getId()));}//方法事务全局事务,这里声明了不自动回滚

Test

Rollback(value=false)publicvoidtestInsert(){Stringjson="{\n"+"\"id\":,\n"+"\"arrivalOrderId\":,\n"+"\"goodsDeployId\":,\n"+"\"expectedReceiveNum\":,\n"+"}";OrderDetailDOorderDetail=TestConsole.toObject(json,ArrivalNoticeOrderDetailDO.class);orderDetail.setId(null);orderDetail.setCaterId(L);//trueAssert.assertTrue(DB.insert(orderDetail)0);//notNullAssert.assertNotNull(DB.selectById(orderDetail.getId()));}}

四、消息验证

eg:

Message入口就类似于Web入口一样。我们复杂的业务逻辑一定不会在入口处直接写代码,如果是这样写的那么维护性和复用性一定是很差的。

五、异步验证

答案是能通过,因为异常代码还没执行到,主线程就结束了。

Testpublicvoidtest(){ExecutorServiceexecutorService=Executors.newFixedThadPool(3);executorService.submit(newRunnable(){

SneakyThrows

Overridepublicvoidrun(){Thad.sleep();Objectobj=null;System.out.println(obj.toString());}});System.out.println("单侧结束");}

5.1white解决简单暴力#

Testpublicvoidtest(){ExecutorServiceexecutorService=Executors.newFixedThadPool(3);executorService.submit(newRunnable(){

SneakyThrows

Overridepublicvoidrun(){Thad.sleep();Objectobj=null;System.out.println(obj.toString());}});System.out.println("单侧结束");white(true);}

5.2LockSupport最大时间限制

Testpublicvoidtest(){ExecutorServiceexecutorService=Executors.newFixedThadPool(3);executorService.submit(newRunnable(){

SneakyThrows

Overridepublicvoidrun(){Thad.sleep();Objectobj=null;System.out.println(obj.toString());}});System.out.println("单侧结束");//挂起指定时间LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(6));}

5.3JUnit定制

5.3.1使用演示

注意这里的

Timed原生是不具备这个能力的,要基于JUnit进行扩展。

Test

Timed(millis=)publicvoidtest(){ExecutorServiceexecutorService=Executors.newFixedThadPool(3);executorService.submit(newRunnable(){

SneakyThrows

Overridepublicvoidrun(){Thad.sleep();System.out.println("任务执行结束");}});System.out.println("单侧结束");}

5.3.2扩展实现

同样是基于LockSupport线程挂起方案,类似于切面解决。

privateMapString,LongtimedMap=newHashMap();privateMapString,LongbefoTestCostMap=newHashMap();

OverridepublicvoidbefoTestMethod(TestContexttestContext)throwsException{Stringkey=testContext.getTestMethod().getName();befoTestCostMap.put(key,System.curntTimeMillis());TimedtimedA=AnnotationUtils.getAnnotation(testContext.getTestMethod(),Timed.class);if(Objects.nonNull(timedA)){timedMap.put(testContext.getTestMethod().getName(),timedA.millis());}MethodtestMethod=testContext.getTestMethod();printActiveProfile(testContext);checkTransactional(testContext);TestConsole.colorPrintln(AnsiColor.BLUE,"西魏陶渊明发起了一个单侧用例:{}#{}",testContext.getTestClass(),testMethod.getName());}

OverridepublicvoidafterTestMethod(TestContexttestContext)throwsException{Stringkey=testContext.getTestMethod().getName();LongafterTestCost=System.curntTimeMillis();LongbefoTestCost=befoTestCostMap.get(key);longtimed=timedMap.get(key);//如果耗时已经大于指定的时间了,就直接过if((timed=0)

afterTestCost-befoTestCosttimed){ThrowabletestException=testContext.getTestException();if(Objects.nonNull(testException)){TestConsole.colorPrintln(AnsiColor.BRIGHT_RED,"测试用例执行失败了,快检查检查吧。");}else{TestConsole.colorPrintln("用例执行成功。");}}else{//如果不够,就要挂起指定时间。(减去0毫秒,给Timed预留的时间)longnanos=TimeUnit.MILLISECONDS.toNanos(timed-(afterTestCost-befoTestCost)-0);//主线程挂起,等待异步执行System.err.printf("Timed任务挂起通知:主线程挂起%ds,等待异步执行%n",TimeUnit.NANOSECONDS.toSeconds(nanos));LockSupport.parkNanos(nanos);}}

5.3.3引导类配置

TestExecutionListeners注意声明添加模式是合并(默认是替换)

Slf4j

ActiveProfiles({"local"})

ContextConfiguration(initializers={BeanLazyApplicationContextInitializer.class})//使用Spring容器引导

RunWith(SpringRunner.class)//合并模式下,增加测试执行监听器

TestExecutionListeners(value=PmsSentryTestExecutionListener.class,mergeMode=TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)//默认就是回滚,不用加

Rollback,如果全局不想回滚就在这个吧

Rollback(false),如果某个单测不想回滚,就放到单侧类上

Transactional

SpringBootTest(classes={CenterProviderApplication.class})//指定启动类publicclassBaseApplicationTest{}




转载请注明:http://www.xxcyfilter.com/gailian/gailian/15681.html