重新同步多线程集成测试

我最近在Captain Debug的Blog上偶然发现了一篇文章“ 同步多线程集成测试 ”。 该文章强调了设计涉及被测类以异步方式运行业务逻辑的集成测试的问题。 给出了这个人为的示例(我删除了一些评论):

public class ThreadWrapper {public void doWork() {Thread thread = new Thread() {@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...");}}

这只是常见模式的一个示例,在该模式中,业务逻辑被委派给我们无法控制的某些异步作业池。 Roger Hughes (作者)列举了测试这种代码的几种技巧,包括:

  • 测试方法中的任意(“足够长”) sleep()以确保后台逻辑完成
  • 重构doWork() ,使其接受CountDownLatch并同意在作业完成时通知它
  • 将上述方法@VisibleForTesting包私有且仅@VisibleForTesting
  • “该”解决方案–重构doWork()使其可以接受任意Runnable 。 在测试中,我们可以包装此Runnable (装饰器模式)并等待内部Runnable完成

最后一个解决方案不错,但是它极大地改变了ThreadWrapper的职责。 现在,由调用者决定在先前ThreadWrapper完全封装业务逻辑时应异步执行哪种作业。 我并不是说这是一个糟糕的设计,但是它与原始方法有很大的不同。

待命性

如果不进行如此大规模的重构,我们可以编写测试吗? 第一个解决方案涉及称为Awaitility的便捷库。 该库不是灵丹妙药,它只是定期评估给定条件并确保它在给定时间内得到满足。 您可能会编写一两次这样的代码,然后将它们包装在具有精心设计的API的库中。 因此,这是我们的初始方法:

import static com.jayway.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;//...await().atMost(10, SECONDS).until(recordInserted());//...private Callable<Boolean> recordInserted() {return new Callable<Boolean>() {@Overridepublic Boolean call() throws Exception {return dataExists();}};
}

我认为这里没有什么可解释的。 dataExists()只是一个boolean方法,最初返回false但一旦后台任务( addDataToDB() )完成,最终将返回true 。 换句话说,我们假设后台任务引入了一些副作用,而dataExists()可以检测到该副作用。 顺便说一句,我碰巧安装了具有Lambda支持的JDK 8,而IntelliJ IDEA给了我这个很好的工具提示:



突然,我得到了建议的与Java 8兼容的替代方案:

private Callable<Boolean> recordInserted() {return () -> dataExists();
}

但是还有更多:

将我的代码转换为:

private Callable<Boolean> recordInserted() {return this::dataExists;
}

this::前缀表示recordInsterted是当前对象的方法。 我们也可以说someDao::dataExists 。 简单地将此语法turns方法放入我们可以传递的函数对象中(此过程在Scala中称为eta扩展 )。 到目前为止,不再需要recordInsterted()方法,因此我可以内联它并将其完全删除:

await().atMost(10, SECONDS).until(this::dataExists);

我不确定我更喜欢什么-新的lambda语法或IntelliJ IDEA如何采用Java 8之前的代码并自动为我进行改版 (嗯,这仍然有点试验性,刚刚报道了IDEA-106670 )。 我可以在IntelliJ项目级的Lambda中运行此意图,从而在几秒钟内启用整个代码库。 甜!

但是回到原来的问题。 通过提供体面的API和一些方便的功能,Awaitility很有帮助。 我将它与FluentLenium广泛结合使用。 但是定期轮询状态变化感觉有点像变通办法,并且仍然引入了最小的延迟。 但是请注意,在异步任务上运行和同步非常普遍,并且JDK已经提供了必要的功能: Future抽象 !

java.util.concurrent.Future

为了限制重构的范围,我将暂时保留原始的new Thread()方法,并使用Guava中的SettableFuture<V> 。 它是Future<V>实现,允许在任何时间从任何线程触发完成或失败(请参阅DeferredResult – Spring MVC中的异步处理以获取更多高级用法)。 如您所见,更改非常小:

public class ThreadWrapper {public ListenableFuture<Void> doWork() {final SettableFuture<Void> future = SettableFuture.<Void>create();Thread thread = new Thread() {@Overridepublic void run() {addDataToDB()//...//last instructionfuture.set(null);}private void addDataToDB() {// Dummy Code...// ...}};thread.start();return future;}}

doWork()现在返回在异步任务内部控制生命周期的ListenableFuture<Void> 。 我们使用Void但实际上您可能想返回一些异步结果。 future.set(null)调用至关重要。 它表示将来已实现,并且将通知所有等待该将来的线程。 再一次,在实践中,您将使用例如Future<Integer> ,然后我们将说future.set(someInteger)而不是null 。 这里null只是Void类型的占位符。 这对我们有什么帮助? 测试代码现在可以依赖将来的完成:

final ListenableFuture<Void> future = wrapper.doWork();
future.get(10, SECONDS);

future.get()阻塞,直到将来完成(超时),即直到我们调用future.set(...)为止。 顺便说一句,我使用的是Guava的ListenableFuture ,但是Java 8引入了等效和标准的CompletableFuture –我将很快写它。

所以,我们到了某个地方。 Future<T>是用于等待和发信号通知后台作业完成的有用抽象。 但也有一个巨大的优势, Future未服用,ekhm,从优势-异常处理和传播。 Future.get()将阻塞,直到Future.get()完成,并返回异步结果引发最初从我们的工作中引发的异常。 这对于异步测试非常有用。 当前,如果Thread.run()引发异常,则它可能会记录也可能不会记录或对我们可见,并且将来将永远无法完成。 使用Awaitility会稍微好一些-它会超时而没有任何有意义的原因,必须在控制台/日志中手动进行跟踪。 但是,只需稍作修改,我们的测试就会更加冗长:

public void run() {try {addDataToDB()//...future.set(null);} catch (Exception e) {future.setException(e);}
}

如果异步作业中发生某些异常,它将弹出并显示为JUnit / TestNG失败原因。

(听)ExecutorService

而已。 如果addDataToDB()引发异常,它将不会丢失。 相反,测试中的future.get()将为我们重新抛出该异常。 我们的测试不会只是超时,也不会让我们知道发生了什么问题。 太好了,但是我们真的必须创建这个特殊的SettableFuture<T>实例,难道我们不能仅使用已经为我们提供Future<T>并具有正确基础实现的现有库吗? 当然! 这样就需要进一步重构:

import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class ThreadWrapper {private final ListeningExecutorService executorService =MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());public ListenableFuture<?> doWork() {Runnable job = new Runnable() {@Overridepublic void run() {//...}};return executorService.submit(job);}}

这就是你们一直在等待的东西。 不要一直启动新Thread ,请使用线程池! 实际上,我通过使用ListeningExecutorService (对ExecutorService的扩展)返回了ListenableFuture实例( 请参阅为什么 ListenableFuture ,进一步走了一步。 但是解决方案不需要这样做,我只是传播了良好的做法。 如您所见,现在已经为我们创建并管理了Future实例。 测试完全相同,但是生产代码更简洁,更可靠。

MoreExecutors.sameThreadExecutor()

我想向您展示的最后一个技巧是依赖注入。 首先,让我们从ThreadWrapper类外部化线程池的创建:

private final ListeningExecutorService executorService;public ThreadWrapper() {this(Executors.newSingleThreadExecutor());
}public ThreadWrapper(ExecutorService executorService) {this.executorService =MoreExecutors.listeningDecorator(executorService);
}

现在,我们可以选择提供自定义ExecutorService 。 出于其他各种原因,这是件好事,但是对我们来说,这提供了全新的测试机会: MoreExecutors.sameThreadExecutor() 。 这次我们稍微修改一下测试:

final ThreadWrapper wrapper = new ThreadWrapper(MoreExecutors.sameThreadExecutor());
wrapper.doWork().get();

看看我们如何通过自定义ExecutorService ? 这是一个非常特殊的实现,它实际上并不维护任何类型的线程池。 每次您向该“池” submit()某个任务时,它将以阻塞方式在同一线程中执行。 这意味着即使生产代码没有太大变化,我们也不再需要异步测试! wrapper.doWork()将阻塞,直到“后台”作业完成。 仍然需要对get()额外的调用,以确保传播了异常,但是保证永远不会阻塞(因为作业已经完成)。

如果您某种程度上依赖于基于线程的属性(例如事务,安全性, ThreadLocal ,则使用同一线程而不是线程池来执行异步任务可能会产生意外结果。 但是,如果您将标准ThreadPoolExecutorCallerRunsPolicy一起CallerRunsPolicy ,则在线程池溢出的情况下,JDK会以这种方式运行。 因此,这并不罕见。

摘要

测试异步代码很困难,但是您可以选择。 几种选择。 但是令我印象深刻的一个结论是我们努力的副作用。 我们重构了原始代码以使其可测试。 但是最终的生产代码不仅可以测试,而且结构和健壮性也更好。 令人惊讶的是,它甚至与以前的版本兼容,因为我们几乎没有将返回类型从void更改为Future<Void>

这似乎是一条规则-可测试的代码通常可以更好地设计和实现。 单元测试是使用我们库的第一个客户端代码。 这自然迫使我们要更多地考虑消费者,而不是实现。

参考:从Java和社区博客的JCG合作伙伴 Tomasz Nurkiewicz 重新审视了同步多线程集成测试 。

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

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

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

相关文章

鸿蒙文化博物馆,有趣、有味、有文化!“周末儿童博物馆”在成博欢乐启幕

昨晚&#xff0c;成都博物馆“周末儿童博物馆”儿童节特别活动“六一欢乐会”拉开帷幕&#xff0c;丰富多彩的各种活动及精彩表演吸引了大批小朋友走进博物馆&#xff0c;提前度过了一个有趣、有味、有文化的“六一”国际儿童节。根据常设展“人与自然&#xff1a;贝林捐赠展”…

Spring MVC:表单处理卷。 3 –复选框处理

我已经发布了有关使用Spring MVC标签库处理checkbox标签的帖子。 现在&#xff0c;我要开发此主题并继续使用“复选框”标签。 它并不难&#xff0c;但是在某些情况下&#xff0c;您最好使用它。 在本文中&#xff0c;我将结合java.util.List和java.util.Map提供Springcheckbox…

html 消息通知声音,ajax实现web页面的消息实时提醒时播放提示音

在应用系统的开发过程中&#xff0c;经常要使用到新消息的提醒功能&#xff0c;比如说后台有一个告警消息&#xff0c;web页面就会实时的收到这个告警的消息&#xff0c;且发出提示音。这其实就是涉及到两个方面的知识&#xff0c;一个是http实时消息的推送&#xff0c;在这儿我…

元素的居中方式总结

最近有点空闲时间&#xff0c;所以想好好看看几个一直没机会看的问题。把它写下来&#xff0c;是促进自己更好地理解&#xff0c;同时也是一个备忘吧&#xff01; 先说元素居中&#xff0c;元素居中&#xff0c;从最开始接触前端就一直挥之不去的一个问题&#xff0c;也许是太…

JArchitect对Java开源贡献者免费

JArchitect是用于Java代码库的静态分析工具&#xff0c;它提供交互式GUI和HTML报告&#xff0c;用于查找代码中过于复杂或有问题的区域&#xff0c;执行分析以重构并比较随时间的变化。 在版本3中&#xff0c;添加了类似LINQ的查询语言&#xff0c;该工具使该工具成为功能极其强…

android让一个控件跟上面控件对其,学个明白--Android控件架构

Android控件架构1.什么是View&#xff1f;View是Android中所有控件的基类。View是界面层的控件的一种抽象&#xff0c;它代表了一个控件。在Android中每个控件都会在界面中占得一块矩形的区域。在Android中控件被分为两类&#xff1a;View和ViewGroup。ViewGroup控件作为父控件…

分享一个自制的计算子网划分的小工具

使用 javascirpt 写的&#xff0c;因此可以使用浏览器浏览即可 code: <meta charset"utf-8">输入划分网段的数量&#xff1a; <input id"inp_netCount" /> <input type"button" οnclick"createElem()" value"sta…

tmux颜色高亮跟vim不一致的情况

安装完tmux之后&#xff0c;按照网上大神的配置&#xff0c;稍微配置了下~/.tmux.conf&#xff1a; # 改变快捷键前缀 unbind C-b set -g prefix C-a # 绑定配置加载按键 bind r source-file ~/.tmux.conf \; display-message "Config reloaded.."# 设置终端类型为2…

html5表白页面3d,七夕节表白3d相册制作(html5+css3)

七夕节表白3d相册制作涉及知识点定位阴影3d转换动画主要思路&#xff1a;通过定位将所有照片叠在一起&#xff0c;在设置默认的样式以及照片的布局&#xff0c;最后通过设置盒子以及照片的旋转动画来达到效果。代码如下&#xff1a;3d相册/* 使用单位将所有照片叠在一起 */img{…

(1)pandas 基础教程

步骤1、环境准备 右击桌面上选择【Open in Terminal】 打开终端。在弹出的终端中输入【ipython】进入Python的解释器中&#xff0c;如图1所示。 图1 ipython解释器步骤2、导入所需要的包 导入实验常用的python包。如图2所示。【import pandas as pd】pandas用来做数据处理。【i…

CSS3效果:波浪效果

实现效果 如图所示&#xff1a; 首先得准备三张图&#xff0c;一张是浅黄色的背景图loading_bg.png&#xff0c;一张是深红色的图loading.png&#xff0c;最后一张为bolang.png。 css代码 body{background:#ffe894;}.loading_bg{width:113px; height:111px;background:url(lo…

html创建文件域的代码,word如何插入域代码

在word里怎么进行域代码的设置&#xff1f;如果知道要插入的域的域代码&#xff0c;可以将其直接键入在文档中。首先按 CtrlF9&#xff0c;然后在括号中键入代码就可以了。【Word插入域方法】1、Word2007中&#xff0c;在要插入域的位置单击。2、在“插入”选项卡上的“文字”组…

前端页面适配的rem换算

为什么要使用rem 之前有些适配做法&#xff0c;是通过js动态计算viewport的缩放值&#xff08;initial-scale&#xff09;。 例如以屏幕320像素为基准&#xff0c;设置1&#xff0c;那屏幕375像素就是375/3201.18以此类推。 但直接这样强制页面缩放过于粗暴&#xff0c;会导致页…

css清除浮动

css设计浮动属性的主要目的&#xff0c;是为了实现文本绕排图片的效果。 一.浮动 当浮动一张图片或者其他元素时&#xff0c;浏览器会将浮动元素往上方推&#xff0c;直到它碰到父元素的内边界。后面的元素不再认为浮动元素在文档流中位于它的前面了&#xff0c;因为它就会占…

gitlab搭建配置;ssh配置;

1.centos7上搭建gitlab&#xff0c;过程略&#xff1b; 命令&#xff1a;gitlab-ctl [start] [stop] [restart] [reconfigure] [tail] 查看gtilab日志 [status] 查看gitlab运行状态信息 2.修改默认ip端口&#xff1a; vim /etc/gitlab/gitlab.rb &#xff1b; external_url h…

家用计算机历史记录,教您如何查看电脑使用记录

很多朋友想查看自己之前使用过的文件或者文档来查询资料&#xff0c;或者是想看电脑是否被人使用过&#xff0c;但是&#xff0c;如何查看电脑使用记录呢&#xff1f;下面系统之家小编教大家查看电脑使用记录小技巧&#xff0c;不用担心找不到电脑使用记录。希望对大家有所帮助…

类的无参方法和Doc注释

一:Java Doc注释: 语法: /** *AccpSchool 类 *author JadeBird *version 1.0 2018/5/26 */ Java Doc是前Sun公司提供的一种技术,它能够从程序代码中抽取类,方法,成员等的注释,形成一个和源代码配套的API帮助文档(简答地说,就是介绍该类,类的方法和成员变量的文档). 因此只要在编…

Spring MVC,Ajax和JSON第3部分–客户端代码

如果您一直关注有关Spring&#xff0c;Ajax和JSON的简短博客系列&#xff0c;那么您会回想起我到目前为止已经创建了一个Spring MVC Web应用程序&#xff0c;该应用程序显示一个表单&#xff0c;该表单允许用户选择一堆项目并向服务器提交购买请求。 然后&#xff0c;服务器用一…

笔记36 Spring Web Flow——配置

Spring Web Flow是一个Web框架&#xff0c;它适用于元素按规定流程运行的程序。Spring Web Flow是Spring MVC的扩展&#xff0c;它支持开发基于流程的应用程 序。它将流程的定义与实现流程行为的类和视图分离开来。在介绍Spring Web Flow的时候&#xff0c;我们将暂时放下Spitt…

一些关于Viewport与device-width的东西~(转)

内容转自 http://www.cnblogs.com/koukouyifan/p/4066567.html 非常感谢 口口一凡 为我们提供的这篇文章&#xff0c;受益匪浅&#xff0c;特地转到自己的博客收藏起来。 以下是原文内容。 进行移动web开发已经有一年多的时间了&#xff0c;期间遇到了一些令人很困惑的东西。…