线程同步,线程不同步_同步多线程集成测试

线程同步,线程不同步

测试线程非常困难,这使得为被测多线程系统编写良好的集成测试非常困难。 这是因为在JUnit中,测试代码,被测对象和任何线程之间没有内置的同步。 这意味着,当您必须为创建并运行线程的方法编写测试时,通常会出现问题。 该领域中最常见的场景之一是调用被测方法,该方法在返回之前启动新线程的运行。 在将来某个时刻完成线程的工作时,您需要断言一切都很好。 这种情况的示例可能包括异步地从套接字读取数据或对数据库执行冗长而复杂的一组操作。

例如,下面的ThreadWrapper类包含一个公共方法: doWork() 。 调用doWork()会使情况doWork()并且在将来某个时候,由JVM决定,一个线程会运行,将数据添加到数据库中。

public class ThreadWrapper {/*** Start the thread running so that it does some work.*/public void doWork() {Thread thread = new Thread() {/*** Run method adding data to a fictitious database*/@Overridepublic void run() {System.out.println("Start of the thread");addDataToDB();System.out.println("End of the thread method");}private void addDataToDB() {// Dummy Code...try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();}}};thread.start();System.out.println("Off and running...");}}

此代码的直接测试是调用doWork()方法,然后在数据库中检查结果。 问题是,由于使用了线程,被测对象,测试对象与线程之间没有协调。 编写此类测试时,实现某种协调的一种常见方法是在被测方法的调用与检查数据库中的结果之间放置某种延迟,如下所示:

public class ThreadWrapperTest {@Testpublic void testDoWork() throws InterruptedException {ThreadWrapper instance = new ThreadWrapper();instance.doWork();Thread.sleep(10000);boolean result = getResultFromDatabase();assertTrue(result);}/*** Dummy database method - just return true*/private boolean getResultFromDatabase() {return true;}
}

在上面的代码中,两个方法调用之间有一个简单的Thread.sleep(10000) 。 这种技术的优点是简单易行。 但是它也非常危险。 这是因为它在测试和工作线程之间引入了竞争条件,因为JVM无法保证线程何时运行。 通常,它只能在开发人员的计算机上工作,而在构建计算机上始终失败。 即使可以在构建机器上运行,它也会从表面上延长测试的持续时间; 请记住,快速构建很重要。 正确执行此操作的唯一肯定方法是同步两个不同的线程,而执行此操作的一种技术是将一个简单的CountDownLatch注入到被测实例中。 在下面的示例中,我修改了ThreadWrapper类的doWork()方法,将CountDownLatch添加为参数。

public class ThreadWrapper {/*** Start the thread running so that it does some work.*/public void doWork(final CountDownLatch latch) {Thread thread = new Thread() {/*** Run method adding data to a fictitious database*/@Overridepublic void run() {System.out.println("Start of the thread");addDataToDB();System.out.println("End of the thread method");countDown();}private void addDataToDB() {try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();}}private void countDown() {if (isNotNull(latch)) {latch.countDown();}}private boolean isNotNull(Object obj) {return latch != null;}};thread.start();System.out.println("Off and running...");}
}

Javadoc API将倒数锁存器描述为:同步辅助,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成为止。 使用给定的计数初始化CountDownLatch。 由于对countDown()方法的调用,当前的await方法将阻塞,直到当前计数达到零为止,此后,所有等待线程都将被释放,并且所有随后的await调用将立即返回。 这是一种一次性现象,无法重置计数。 如果需要用于重置计数的版本,请考虑使用CyclicBarrier。

CountDownLatch是一种多功能的同步工具,可以用于多种用途。 以1计数初始化的CountDownLatch用作简单的开/关闩锁或门:所有调用await的线程在门处等待,直到被调用countDown()的线程打开为止。 初始化为N的CountDownLatch可以用于使一个线程等待,直到N个线程完成某项操作或某项操作已完成N次。 CountDownLatch的一个有用属性是,它不需要调用countDown的线程在继续进行操作之前就无需等待计数达到零,它只是防止任何线程经过等待状态,直到所有线程都可以通过。

这里的想法是,测试代码将永远不会检查数据库的结果,直到工作线程的run()方法调用latch.countdown() 。 这是因为测试代码线程阻塞了对latch.await()的调用。 闩锁latch.countdown()减少闩锁的计数,并且一旦它为零,阻塞调用闩锁latch.await()将返回并且测试代码将继续执行,这是安全的, latch.await()是应知道数据库中应有任何结果。 然后,测试可以检索这些结果并做出有效的断言。 显然,以上代码仅伪造了数据库连接和操作。 问题是您可能不想或不需要直接将CountDownLatch插入代码中。 毕竟它没有在生产中使用,而且看起来也不是特别干净或优雅。 解决此问题的一种快速方法是简单地将doWork(CountDownLatch latch)方法包设为私有,并通过公共doWork()方法公开它。

public class ThreadWrapper {/*** Start the thread running so that it does some work.*/public void doWork() {doWork(null);}@VisibleForTestingvoid doWork(final CountDownLatch latch) {Thread thread = new Thread() {/*** Run method adding data to a fictitious database*/@Overridepublic void run() {System.out.println("Start of the thread");addDataToDB();System.out.println("End of the thread method");countDown();}private void addDataToDB() {try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();}}private void countDown() {if (isNotNull(latch)) {latch.countDown();}}private boolean isNotNull(Object obj) {return latch != null;}};thread.start();System.out.println("Off and running...");}
}

上面的代码使用Google的Guava @VisibleForTesting批注来告诉我们,出于测试目的,已经稍微放松了doWork(CountDownLatch latch)方法的可见性。

现在,我意识到,将一个方法调用包私有化以用于测试目的是非常有争议的; 有些人讨厌这个主意,而另一些人则无所不在。 我可以就这个主题写一个整个博客(可能一天),但是对我来说,在别无选择的情况下(例如,当您为遗留代码编写特性测试时)应谨慎使用。 如果可能,应避免使用它,但决不能排除。 毕竟,经过测试的代码比未经测试的代码更好。

考虑到这一点, ThreadWrapper的下一次迭代将设计出标记为@VisibleForTesting的方法,以及将CountDownLatch注入生产代码的需求。 这里的想法是使用策略模式并将Runnable实现与Thread分开。 因此,我们有一个非常简单的ThreadWrapper

public class ThreadWrapper {/*** Start the thread running so that it does some work.*/public void doWork(Runnable job) {Thread thread = new Thread(job);thread.start();System.out.println("Off and running...");}
}

和一个单独的工作:

public class DatabaseJob implements Runnable {/*** Run method adding data to a fictitious database*/@Overridepublic void run() {System.out.println("Start of the thread");addDataToDB();System.out.println("End of the thread method");}private void addDataToDB() {try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();}}
}

您会注意到DatabaseJob类不使用CountDownLatch 。 如何同步? 答案就在下面的测试代码中……

public class ThreadWrapperTest {@Testpublic void testDoWork() throws InterruptedException {ThreadWrapper instance = new ThreadWrapper();CountDownLatch latch = new CountDownLatch(1);DatabaseJobTester tester = new DatabaseJobTester(latch);instance.doWork(tester);latch.await();boolean result = getResultFromDatabase();assertTrue(result);}/*** Dummy database method - just return true*/private boolean getResultFromDatabase() {return true;}private class DatabaseJobTester extends DatabaseJob {private final CountDownLatch latch;public DatabaseJobTester(CountDownLatch latch) {super();this.latch = latch;}@Overridepublic void run() {super.run();latch.countDown();}}
}

上面的测试代码包含一个内部类DatabaseJobTester ,该类扩展了DatabaseJob 。 在此类中,在通过调用super.run()更新了我们的虚假数据库之后,将run()方法重写为包括对latch.countDown()的调用。 之所以doWork(Runnable job) ,是因为测试将DatabaseJobTester实例传递给doWork(Runnable job)方法,并添加了所需的线程测试功能。 我曾在我的一篇有关测试技术的博客中提到过将被测试对象分类的想法,这是一种非常强大的技术。

因此,得出以下结论:

  • 测试线程很难。
  • 测试匿名内部类几乎是不可能的。
  • 使用Thead.sleep(...)是一个冒险的想法,应避免使用。
  • 您可以使用策略模式来重构这些问题。
  • 编程是做出正确决策的艺术

…放松测试方法的可视性可能是一个好主意,也许不是一个好主意,但稍后会更多……

上面的代码可在unit-testing-threads项目下的队长调试存储库(git://github.com/roghughe/captaindebug.git)中的Github上找到。

参考: Captain Debug的Blog博客上的JCG合作伙伴 Roger Hughes的同步多线程集成测试 。

翻译自: https://www.javacodegeeks.com/2013/02/synchronising-multithreaded-integration-tests.html

线程同步,线程不同步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/349545.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ehcache 默认大小_简单的使用ehcache

ehcache是一个用Java实现的使用简单,高速,实现线程安全的缓存管理类库,ehcache提供了用内存,磁盘文件存储,以及分布式存储方式等多种灵活的cache管理方案。同时ehcache作为开放源代码项目,采用限制比较宽松…

JS定时器使用,定时定点,固定时刻,循环执行

本文概述:本文主要介绍通过JS实现定时定点执行,在某一个固定时刻执行某个函数的方法。比如说在下一个整点执行,在每一个整点执行,每隔10分钟定时执行的方法。 JavaScript中有两个定时器方法:setTimeout(&am…

axios 跨域_当遇到跨域开发时,我们如何处理好前后端配置和请求库封装

我们知道很多大型项目都或多或少的采用跨域的模式开发, 以达到服务和资源的解耦和高效利用. 在大前端盛行的今天更为如此, 前端工程师可以通过nodejs或者Nginx轻松搭建起web服务器.这个时候我们只需要请求后端服务器的接口即可实现系统的业务功能开发.这个过程中会涉及到web页面…

Java数据类型和标识符

在本教程中,我们将了解Java中的数据类型和标识符。 Java语言具有丰富的数据类型实现。 数据类型指定大小和可以存储在标识符中的值的类型。 Java数据类型分为两类: 原始数据类型 非原始数据类型 原始类型 Java定义了八种原始数据类型:字…

哈罗顺风车送到终点吗_没有了顺风车,滴滴“特惠拼车”来了!比拼车更低价,比顺风车更安全吗?...

近日,有网友表示,滴滴出行App内出现了“特惠拼车”功能,据悉,该功能主要给乘客提供长距离拼车出行的优惠折扣。如果拼车价格为44.8元,那“特惠拼车”的价格为30.7元。但是该功能目前还未在上海出现。近日,有…

管理角色认知-工程师到管理者角色发生了哪些变化?

背景 不同等级的管理者需求不同; 管理者需求说明新经理提供管理相关的工具和方法能力层面,术高级经理提升角色认知认知和理解,道,系统通过认知上的改变达到能力和行为上的改善一个人的行为,能力,价值观都源…

axure 小程序 lib_小程序定制开发的步骤有哪些?

经过两年多的微信小程序开发,各种功能应用变得越来越成熟,越来越多的企业和企业正在开发微信小程序用于在线营销。如果您的公司尚未开发成都小程序,它将变得越来越凶猛。在竞争激烈的市场环境中,将失去许多获得和营销客户的机会。…

java 检查打印机状态_爱普生打印机常见故障有哪些 爱普生打印机故障解决方法【详解】...

任何设备在使用一段时间后都会出现一些小问题,打印机也不例外,那么爱普生打印机常见的故障有哪些 ,出现问题该怎么解决呢?下面小编就来分享2018爱普生打印机故障解决方法 ,一起来看看吧!一、故障现象 &a…

eureka 之前的服务如何关闭_干货分享 | 服务注册中心Spring Cloud Eureka部分源码分析...

友情提示:全文13000多文字,预计阅读时间10-15分钟Spring Cloud Eureka作为常用的服务注册中心,我们有必要去了解其内在实现机制,这样出现问题的时候我们可以快速去定位问题。当我们搭建好Eureka Server服务注册中心并启动后&#…

局部变量写在循环内还是外_循环内的局部变量和性能

局部变量写在循环内还是外总览 有时会出现一个问题,即分配一个新的局部变量需要多少工作。 我的感觉一直是,代码已优化到成本为静态的程度,即一次执行,而不是每次运行都执行一次。 最近, Ishwor Gurung建议考虑将一些…

csp-s模拟测试44「D·E·F」

用心出题,用脚造数据 乱搞场 1 #include<bits/stdc.h>2 #define re register3 #define int long long4 #define inf 0x7ffffffffffffff5 using namespace std;6 int n,a[100010],b[100010],ansinf;7 double st,ed;8 inline int read(){9 re int a0,b1; re char chget…

c++中的new_怎么在java中创建一个自定义的collector

简介在之前的java collectors文章里面&#xff0c;我们讲到了stream的collect方法可以调用Collectors里面的toList()或者toMap()方法&#xff0c;将结果转换为特定的集合类。今天我们介绍一下怎么自定义一个Collector。Collector介绍我们先看一下Collector的定义&#xff1a;Co…

Java 9中的新Regex功能

最近&#xff0c;我收到了Packt出版的Anubhava Srivastava提供的免费书籍“ Java 9 Regular Expressions” 。 这本书是一个很好的教程&#xff0c;它向任何想学习正则表达式并从头开始的人介绍。 那些知道如何使用正则表达式的人可能仍然很有趣&#xff0c;以重申其知识并加深…

c语言实现二分法_C语言实现二分法求解方程在区间内的根

C语言实现二分法求解方程在区间内的根。设有非线性方程&#xff1a;其中&#xff0c; 为 上连续函数且设 (不妨设方程在 内仅有一个实根)&#xff0c;求上述方程实根的二分法过程&#xff0c;就是将含根区间[a,b]逐步分半&#xff0c;检查函数值符号的变化&#xff0c;以便确定…

计划

赤 wqs二分 https://www.cnblogs.com/Juve/p/11479423.html https://www.cnblogs.com/Rorschach-XR/p/11479602.html 反悔贪心 https://www.cnblogs.com/cjyyb/p/9367948.html https://www.cnblogs.com/Miracevin/p/9795871.html https://blog.csdn.net/weixin_34344677/articl…

java写入文件编码格式为ansi_Windows10 bat批处理删除 快速打开文件夹 固定到开始菜单或任务栏...

1. 快速打开文件夹1.1 bat文件如果你经常打开一个被放在很深的文件夹&#xff0c;于是浪费很多打开文件的时间&#xff1a;开始>此电脑>Data(D:)>文件夹1>文件夹2...>文件夹n>平均要浪费大约10秒的时间&#xff0c;如果每天打开100次&#xff0c;那么1000秒&…

python中__init__.py的作用、module和package

控制包的导入行为&#xff1a; 1、声明当前文件是一个可导入的包&#xff1b;2、如果当下包下有多个.py文件使用__ all__ [ 模块名]&#xff0c;也就是form XXX import YYYmodule和package 为了编写可维护的代码&#xff0c;我们把很多函数分组&#xff0c;分别放到不同的文件…

java default修饰符_Java基础语法二

Java循环结构for循环/* for(初始化;布尔表达式;更新) {执行代码 } */ for(int i0; i < 5; i) {System.out.println(i); }最先执行初始化步骤&#xff1b;可以声明一种类型&#xff0c;但可初始化一个或多个循环控制变量&#xff0c;也可以是空语句然后检测布尔表达式&#x…

微信开发修改button里的字体大小_微信小程序全栈开发课程【视频版】2.2 index页面完善...

​点击观看视频课程 ↓↓↓微信小程序首页完善https://www.zhihu.com/video/1195308096099282944课程文字版1、template 部分&#xff08;1&#xff09;修改template部分的代码template里面包含html代码&#xff0c;对应着原生小程序框架里中的.wxml文件。我们将index.vue文件中…

jaxb list集合对象_JAXB –表示空集合和空集合

jaxb list集合对象示范代码 以下演示代码将用于Java模型的所有不同版本。 它只是将一个集合设置为null&#xff0c;第二个设置为空列表&#xff0c;第三个设置为填充列表。 package package blog.xmlelementwrapper;import java.util.ArrayList; import javax.xml.bind.*;publ…