线程同步,线程不同步_同步多线程集成测试
线程同步,线程不同步
测试线程非常困难,这使得为被测多线程系统编写良好的集成测试非常困难。 这是因为在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
线程同步,线程不同步
线程同步,线程不同步_同步多线程集成测试相关推荐
- 同步电复律英文_同步电复律与非同步电复律有什么区别?
展开全部 同步电除颤的适应症是治疗--房颤.房扑.室32313133353236313431303231363533e59b9ee7ad9431333365653161上速.室速等快速心律失常,经电除 ...
- 同步电复律英文_同步电复律操作规程
同步电复律操作规程 目的 中止血液波动力学不稳定的心动过速性心律失常, 同时也可将血液动力 学稳定的房颤或房扑转为窦律. 相关知识 1 同步电复律是指同步触发装置能利用患者心电图中 R 波来触发 放电 ...
- 同步fifo的串并_同步FIFO设计Spec(示例代码)
为什么要写Spec文档: 记得刚进公司实习的时候,导师安排我写一个SM4算法AHB接口模块,要求写代码前 写出详细的设计文档,详细到什么程度呢,看着文档就能把代码写好,作为一个只 在学校写过数字钟的小 ...
- java 同步与异步区别_同步和异步有何异同,在什么情况下分别使用它们?
2015-05-12 06:30:01 阅读( 4 ) 通俗版:举个例子:普通B/S模式(同步)AJAX技术(异步) 同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器 ...
- 同步fifo的串并_同步FIFO笔记
FIFO简介:First in First out,先进先出的数据结构.只能顺序的读入数据,顺序的读出数据. 使用RAM进行建模.设计相应的控制模块,控制RAM中的数据写入和读取,先写入的数据先读取. ...
- 同步fifo的串并_同步fifo
利用verilog实现FIFO 摘要:本文先介绍了一下关于FIFO的基本概念,工作原理,功能,同步与异步的分类等.然后基于RAM实现了一个同步FIFO.该FIFO通过巧妙地应用地址位和状态位的结合实现 ...
- java线程同步机制有哪些_多线程同步机制包括哪些,java线程同步机制
多线程同步机制包括哪些什么是多线程同步机制,多线程同步机制包括:1.临界段用于实现"独占占有":2.信号量用于跟踪有限的资源:3.互斥是核心对象,可以实现不同线程之间的" ...
- 线程同步,线程不同步_重新同步多线程集成测试
线程同步,线程不同步 我最近在Captain Debug的Blog上偶然发现了一篇文章" 同步多线程集成测试 ". 那篇文章强调了设计涉及异步运行业务逻辑的被测类的集成测试的问题. ...
- 多线程是并行还是并发_并发,并行,线程,进程,异步和同步有相关性吗?
本文翻译自:https://medium.com/swift-india/concurrency-parallelism-threads-processes-async-and-sync-relate ...
最新文章
- 围棋棋盘上的波粒二象性
- things to do in English debate: scenario
- NeHe OpenGL课程 网址整理
- 正则表达式小括号的多义性
- 关于单页面应用一些随想
- 查找对方IP地址经典技巧汇总
- 数字证书、ssl、sasl(GSSAPI,Kerberos)、jaas简单解释
- 用友nc6.5详细安装过程
- 进程调度算法Java
- 如何解决遇到的The server time zone value ‘?й???????‘ is unrecognized or represents more than one time zone
- 即时通讯软件都有哪些类型?哪些适合企业内部使用?
- ps一点等于多少厘米_在ps中1厘米是多少像素
- 穿越NAT的p2p通信方法研究
- 文件系统层次结构标准和Linux上下载源代码配置编译安装
- 力扣算法题-19.秋叶收藏集 C语言实现
- 什么是STAR原则?
- CANoe/CAPL ,钉钉群助手消息通知
- 产品结构设计的主要内容有哪些?
- (1)计算机网络基础知识之网络的构成要素
- Android期末作品