同步多线程集成测试

测试线程非常困难,这使得为要测试的多线程系统编写良好的集成测试非常困难。 这是因为在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用给定的计数初始化。 由于countCount()方法的调用,直到当前计数达到零为止,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/369147.shtml

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

相关文章

调整图像的灰度级数C++实现

图像灰度级数我们见得最多的就是256了,如果想调整它的灰度级数,我们可以使用图像库的imadjust函数来作出调整,比如讲256个灰度级变成2个灰度级(也就是二值图了)。再举一个例子,原来一幅256个灰度级的图像&a…

angular4获得焦点事件_Angular 4 文本框自动获取焦点二

Angular是不推荐直接通过DOM操作获取元素的,要操作元素就通过ViewChild装饰器。在HTML中对元素添加引用myInput:在ts中可以通过ViewChild获取指定元素的引用:import { ViewChild } from angular/core;ViewChild(myInput) input;获取到对应元素…

Bootstrap源码解读之栅格化篇

本文纯属自己研究所写笔记,如果有错误还请多多指教提出 版心(container) 版心:class名为.container的容器,其版心的宽度在各个屏幕设备下是不一样的值,版心两边就是留白。 各尺寸下版心宽度如下表: 屏幕设备版心宽度ma…

EasyCriteria 2.0 – JPA标准应该很容易

在今天的帖子中,我们将看到名为EasyCriteria的框架的新版本。 在这篇文章的结尾,我们将在博客中看到这里的内容。 不幸的是,JPA标准存在一个巨大的问题,即冗长。 为什么不变得更简单? 像这样认为EasyCriteria框架已经诞…

[BZOJ2095][Poi2010]Bridges 最大流(混合图欧拉回路)

2095: [Poi2010]Bridges Time Limit: 10 Sec Memory Limit: 259 MBDescription YYD为了减肥,他来到了瘦海,这是一个巨大的海,海中有n个小岛,小岛之间有m座桥连接,两个小岛之间不会有两座桥,并且从一个小岛…

excel和python建模_利用Excel学习Python:准备篇

写在前面这个系列我们要利用Excel的知识,学会用python进行数据分析,如果你精通Excel想要用python提高数据分析效率,那么这个系列你来对了,如果你已经是python大神,想要建模/算法等高级技巧的,这个系列可能不…

故障公告:IIS应用程序池停止工作造成博客站点无法访问

非常抱歉,今天凌晨博客站点负载均衡中所有3台服务器的IIS应用程序池突然停止工作,造成 1:20-7:45 左右博客站点无法正常访问,由此给您带来很大的麻烦,请您谅解。 服务器操作系统是 Windows Server 2016,对应的 IIS 错误…

前端HTML以及HTML5(基本标签)

前面一章介绍了一下前端的发展,这章简单介绍一下html的发展以及基本的标签。 一、HTML的发展史 1、概念 超文本标记语言(HyperText Markup Language,简称HTML)是为 [ 网页创建和其他可在浏览器中看到的信息 ] 设计的一种标记语言…

方法内联在JVM中有多积极?

IntelliJ IDEA中使用Ctrl Alt M 提取方法 。 Ctrl Alt M。 这就像选择一段代码并按此组合一样简单。 Eclipse也有它 。 我讨厌冗长的方法。 对于我来说,闻起来太久了: public void processOnEndOfDay(Contract c) {if (DateUtils.addDays(c.getCrea…

Python正则表达式基础

1. 正则表达式基础 1.1. 简单介绍 正则表达式并不是Python的一部分。正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分强大。得益于这一点,在提供了正则表…

hexdump mysql_linux下mysql数据库定时备份

备份操作命令:mysqldump -uroot --default-character-setutf8 --hex-blob -p123456 test_oa > /usr/software/data_backup/mysql_backup/test.sql以下是完整脚本,加--default-character-setutf8 --hex-blob 防乱码发生。#!/bin/bashdatabasestestus…

新建一个页面

今天我刚好要做一个单页面来展示某些东西。 就一起来看看吧&#xff0c;初学者写的不好请自闭双眼。 先上代码吧&#xff0c;大家看看有什么需要修改的地方。 1 <!DOCTYPE html>2 <html lang"en">3 4 <head>5 <meta charset"UTF-8&q…

Java并发:隐藏线程死锁

大多数Java程序员熟悉Java线程死锁概念。 它本质上涉及2个线程&#xff0c;它们彼此永远等待。 这种情况通常是平面&#xff08;同步&#xff09;或ReentrantLock&#xff08;读或写&#xff09;锁排序问题的结果。 Found one Java-level deadlock:"pool-1-thread-2"…

vue中使用axios发送请求

我们知道&#xff0c;vue2.0以后&#xff0c;vue就不再对vue-resource进行更新&#xff0c;而是推荐axios&#xff0c;而大型项目都会使用 Vuex 来管理数据&#xff0c;所以这篇博客将结合两者来发送请求 1.安装axios cnpm i axios -S 2.方案一&#xff1a;修改原型链 首先&…

django缓存

由于Django是动态网站&#xff0c;所有每次请求均会去数据进行相应的操作&#xff0c;当程序访问量大时&#xff0c;耗时必然会更加明显&#xff0c;最简单解决方式是使用&#xff1a;缓存&#xff0c;缓存将一个某个views的返回值保存至内存或者memcache中&#xff0c;5分钟内…

linux 输入法成繁体字_寻找Ubuntu中繁体字输入法

当客户来自港台地区时&#xff0c;英文和繁体字就成了交流的主要工具。windows下我们有搜狗输入法可以切换简体与繁体&#xff0c;那么Ubuntu下怎么办&#xff1f;這是我第一次考慮這個問題&#xff0c;在我的印象裏Linux下的中文輸入法還不是那麼完善&#xff0c;所以我進行了…

vue跨域解决方法

vue跨域解决方法 vue项目中&#xff0c;前端与后台进行数据请求或者提交的时候&#xff0c;如果后台没有设置跨域&#xff0c;前端本地调试代码的时候就会报“No Access-Control-Allow-Origin header is present on the requested resource.” 这种跨域错误。 要想本地正常的调…

Spring 3.2的REST异常处理

1.概述 本文将重点介绍如何使用REST API的Spring实现异常处理 。 我们将介绍在Spring 3.2之前可用的较旧的解决方案&#xff0c;然后是对Spring 3.2的新支持。 本文的主要目的是展示如何最好地将应用程序中的异常映射到HTTP状态代码。 哪种状态代码不适合本文中的哪种情况&…

kali中常用的ctf工具

exiftool:查看图片的exif信息。 pngcheck:修复被破坏的png图片 pngtools:深入研究png文件的数据 steganographic&#xff1a;用来提取图片中的隐藏信息 stegsolve.jar:kali中没有该工具&#xff0c;但是可以自己下 gimp:提供了转换各类图像文件可视化数据的功能&#xff0c;还可…

linux将所有文件生成lst_Linux自定义repo文件

repo文件简介repo文件是CentOS中yum源(软件仓库)的配置文件&#xff0c;通常一个repo文件定义了一个或者多个软件仓库的细节内容&#xff0c;例如我们将从哪里下载需要安装或者升级的软件包&#xff0c;repo文件中的设置内容将被yum读取和应用yum原理YUM的工作原理并不复杂&…