这是一些技巧,说明如何进行代码的逻辑正确性测试(与多线程正确性相对)。

我发现本质上有两种带有线程代码的刻板印象模式:

  1. 面向任务–许多短期运行的同类任务,通常在Java 5执行程序框架内运行,
  2. 面向流程–很少,长时间运行的异构任务,通常基于事件(等待通知)或轮询(周期之间休眠),通常使用线程或可运行的方式表示。

测试这两种类型的代码可能很难。 该工作是在另一个线程中完成的,因此完成通知可能是不透明的,或者隐藏在抽象级别的后面。

该代码在GitHub上 。

提示1 –生命周期管理对象

具有生命周期受管理的对象更易于测试,该生命周期允许设置和拆卸,这意味着您可以在测试后进行清理,而没有乱码干扰任何其他测试。

public class Foo {private ExecutorService executorService;public void start() {executorService = Executors.newSingleThreadExecutor();}public void stop() {executorService.shutdown();}
}

技巧2 –设置测试超时

代码中的错误(如下所示)可能导致多线程测试永远不会完成,例如(例如)您正在等待从未设置的标志。 JUnit允许您设置测试超时。

...
@Test(timeout = 100) // in case we never get a notification
public void testGivenNewFooWhenIncrThenGetOne() throws Exception {
...

技巧3 –在与测试相同的线程中运行任务

通常,您将拥有一个在线程池中运行任务的对象。 这意味着您的单元测试可能必须等待任务完成,但是您不知道什么时候完成。 您可能会猜测,例如:

public class Foo {private final AtomicLong foo = new AtomicLong();
...public void incr() {executorService.submit(new Runnable() {@Overridepublic void run() {foo.incrementAndGet();}});}
...public long get() {return foo.get();}
}
public class FooTest {private Foo sut; // system under test@Beforepublic void setUp() throws Exception {sut = new Foo();sut.start();}@Afterpublic void tearDown() throws Exception {sut.stop();}@Testpublic void testGivenFooWhenIncrementGetOne() throws Exception {sut.incr();Thread.sleep(1000); // yuk - a slow test - don't do thisassertEquals("foo", 1, sut.get());}
}

但这是有问题的。 执行是不统一的,因此不能保证它可以在另一台机器上运行。 它很脆弱,对代码的更改可能会导致测试失败,因为它突然花费了太长时间。 它的速度很慢,因为当它失败时您会大方入睡。

一个诀窍是使任务同步运行,即与测试在同一线程中运行。 这可以通过注入执行程序来实现:

public class Foo {
...public Foo(ExecutorService executorService) {this.executorService = executorService;}
...public void stop() {// nop
}

然后,您可以使用同步执行程序服务(概念类似于SynchronousQueue)进行测试:

public class SynchronousExecutorService extends AbstractExecutorService {private boolean shutdown;@Overridepublic void shutdown() {shutdown = true;}@Overridepublic List<Runnable> shutdownNow() {shutdown = true; return Collections.emptyList();}@Overridepublic boolean isShutdown() {shutdown = true; return shutdown;}@Overridepublic boolean isTerminated() {return shutdown;}@Overridepublic boolean awaitTermination(final long timeout, final TimeUnit unit) {return true;}@Overridepublic void execute(final Runnable command) {command.run();}
}

不需要睡觉的更新测试:

public class FooTest {private Foo sut; // system under testprivate ExecutorService executorService;@Beforepublic void setUp() throws Exception {executorService = new SynchronousExecutorService();sut = new Foo(executorService);sut.start();}@Afterpublic void tearDown() throws Exception {sut.stop();executorService.shutdown();}@Testpublic void testGivenFooWhenIncrementGetOne() throws Exception {sut.incr();assertEquals("foo", 1, sut.get());}
}

请注意,您需要从外部对Foo的执行程序进行生命周期管理。

技巧4 –从线程中提取工作

如果您的线程正在等待一个事件,或者正在等待某个时间,则将其提取到自己的方法中并直接调用它。 考虑一下:

public class FooThread extends Thread {private final Object ready = new Object();private volatile boolean cancelled;private final AtomicLong foo = new AtomicLong();@Overridepublic void run() {try {synchronized (ready) {while (!cancelled) {ready.wait();foo.incrementAndGet();}}} catch (InterruptedException e) {e.printStackTrace(); // bad practise generally, but good enough for this example}}public void incr() {synchronized (ready) {ready.notifyAll();}}public long get() {return foo.get();}public void cancel() throws InterruptedException {cancelled = true;synchronized (ready) {ready.notifyAll();}}
}

而这个测试:

public class FooThreadTest {private FooThread sut;@Beforepublic void setUp() throws Exception {sut = new FooThread();sut.start();Thread.sleep(1000); // yukassertEquals("thread state", Thread.State.WAITING, sut.getState());}@Afterpublic void tearDown() throws Exception {sut.cancel();}@Afterpublic void tearDown() throws Exception {sut.cancel();}@Testpublic void testGivenNewFooWhenIncrThenGetOne() throws Exception {sut.incr();Thread.sleep(1000); // yukassertEquals("foo", 1, sut.get());}
}

现在提取工作:

@Overridepublic void run() {try {synchronized (ready) {while (!cancelled) {ready.wait();undertakeWork();}}} catch (InterruptedException e) {e.printStackTrace(); // bad practise generally, but good enough for this example}}void undertakeWork() {foo.incrementAndGet();}

重构测试:

public class FooThreadTest {private FooThread sut;@Beforepublic void setUp() throws Exception {sut = new FooThread();}@Testpublic void testGivenNewFooWhenIncrThenGetOne() throws Exception {sut.incr();sut.undertakeWork();assertEquals("foo", 1, sut.get());}
}

提示5 –通过事件通知状态更改

前面两个技巧的替代方法是使用通知系统,以便您的测试可以侦听线程对象。

这是一个面向任务的示例:

public class ObservableFoo extends Observable {private final AtomicLong foo = new AtomicLong();private ExecutorService executorService;public void start() {executorService = Executors.newSingleThreadExecutor();}public void stop() {executorService.shutdown();}public void incr() {executorService.submit(new Runnable() {@Overridepublic void run() {foo.incrementAndGet();setChanged();notifyObservers(); // lazy use of observable}});}public long get() {return foo.get();}
}

及其对应的测试(注意使用超时):

public class ObservableFooTest implements Observer {private ObservableFoo sut;private CountDownLatch updateLatch; // used to react to event@Beforepublic void setUp() throws Exception {updateLatch = new CountDownLatch(1);sut = new ObservableFoo();sut.addObserver(this);sut.start();}@Overridepublic void update(final Observable o, final Object arg) {assert o == sut;updateLatch.countDown();}@Afterpublic void tearDown() throws Exception {sut.deleteObserver(this);sut.stop();}@Test(timeout = 100) // in case we never get a notificationpublic void testGivenNewFooWhenIncrThenGetOne() throws Exception {sut.incr();updateLatch.await();assertEquals("foo", 1, sut.get());}
}

这有优点和缺点:

优点:

  1. 创建用于侦听对象的有用代码。
  2. 可以利用现有的通知代码,这使其成为已经存在的一个不错的选择。
  3. 更加灵活,可以同时应用于任务和面向过程的代码。
  4. 它比提取工作更具凝聚力。

缺点:

  1. 侦听器代码可能很复杂,并且会带来自己的问题,从而创建了应测试的其他生产代码。
  2. 将提交与通知分离。
  3. 要求您处理没有发送通知的情况(例如由于错误)。
  4. 测试代码可能很冗长,因此容易出错。

参考: Alex Collins博客博客中来自JCG合作伙伴 Alex Collins的5条关于单元测试线程代码的技巧 。

翻译自: https://www.javacodegeeks.com/2012/09/5-tips-for-unit-testing-threaded-code.html

单元测试线程代码的5个技巧相关推荐

  1. 单元测试怎么测试线程_单元测试线程代码的5个技巧

    单元测试怎么测试线程 以下是一些技巧,说明如何进行代码的逻辑正确性测试(与多线程正确性相对). 我发现本质上有两种带有线程代码的刻板印象模式: 面向任务-许多短期运行的同类任务,通常在Java 5执行 ...

  2. java线程的5个使用技巧

    Java线程的5个使用技巧 Published: 21 Jan 2015 Category: java Java线程有哪些不太为人所知的技巧与用法? 萝卜白菜各有所爱.像我就喜欢Java.学无止境,这 ...

  3. 【RDMA】优化 RDMA 代码的提示和技巧

    目录 RDMA性能优化理论依据 二.基础概念背后的硬件执行方式和原理 Memory Region RDMA Verbs Queue Pair 三.RDMA性能优化 3.1 关注地址翻译的性能开销 3. ...

  4. java结束全部操作代码_Java创建与结束线程代码示例

    这篇文章主要介绍了Java创建与结束线程代码示例,小编觉得挺不错的,这里分享给大家,供需要的朋友参考. 本文讲述了在Java中如何创建和结束线程的最基本方法,只针对于Java初学者.一些高级知识如线程 ...

  5. 求一个简单的java线程代码,Java线程代码的实现方法

    1.继承Thread 声明Thread的子类 运行thread子类的方法 2.创建Thread的匿名子类 3.实现Runnable接口 声明 运行 4.创建实现Runnable接口的匿名类 5.线程名 ...

  6. 盘点 10 个代码重构的小技巧

    本次我们抛开 JAVA 虚拟机源码这些相对底层的东西,LZ 来与各位探讨一下几个代码重构的小技巧,这些内容部分来自于书籍当中,部分来自于 LZ 维护项目当中的一些实践经验.如果猿友们曾经用过这种手法, ...

  7. java线程代码实现_Java 多线程代码实现讲解

    作为一个完全面向对象的语言,Java提供了类 java.lang.Thread 来方便多线程编程,这个类提供了大量的方法来方便我们控制自己的各个线程.那么如何提供给 Java 我们要线程执行的代码呢? ...

  8. java创建线程代码_Java创建与结束线程代码示例

    本文讲述了在Java中如何创建和结束线程的最基本方法,只针对于Java初学者.一些高级知识如线程同步.调度.线程池等内容将会在后续章节中逐步深入. 创建线程 创建普通线程有两种方式,继承Thread类 ...

  9. DLL内线程同步主线程研究(子线程代码放到主线程执行)

    DLL内线程同步主线程研究(子线程代码放到主线程执行) 我们在实际项目中经常会用到多线程编程,比如Socket编程等,在创建的线程内同步主线程一般使用Synchronize方法实现子线程操作放到主线程 ...

最新文章

  1. 通俗解释AWS云服务每个组件的作用
  2. 基于单片机的倒车雷达系统设计c语言,基于AT89C2051单片机实现超声波倒车雷达系统的设计...
  3. XamarinEssentials教程设置首选项Preferences的值
  4. Go 语言 XML处理
  5. mysql如何进行压测_详解MySQL如何按表创建千万级的压测数据
  6. 加快LOOP嵌套循环的一个方法
  7. 80后创业故事之:兄弟散伙,创业失败(转)
  8. 在php中type有几种属性,HTML表单之input元素的23种type类型
  9. 选择排序 冒泡排序 二分查找
  10. 总结:Sharepoint2010 Client Object Model -- ECMAScript Client
  11. MATLAB-基本简介
  12. JavaScript生成uuid
  13. 怎么用计算机名称共享打印机设置,如何共享打印机设置教程
  14. c语言开发 kdj,[转载]随机指标KDJ,及其MA、EMA、SMA、DMA介绍
  15. 今日金融词汇---后复权,是什么?
  16. QoS(服务质量)指标
  17. 计算机组成原理笔记(王道考研) 第六章:总线
  18. CC3200学习总结
  19. 基于深度学习的图像超分辨率方法 总结
  20. 纽约大学计算机工程专业课程,NYU的Electrical and Computer Engineering「纽约大学电气与计算机工程系」...

热门文章

  1. 可以搜python编程答案的软件_python实现百万答题自动百度搜索答案
  2. linux-basic(12)正则表达式与文件格式化处理
  3. java的几种对象(PO,VO,DAO,BO,POJO)解释
  4. java rest框架_比较Java REST文档框架
  5. apache lucene_Apache Lucene基础教程
  6. MongoDB初学者教程
  7. gradle构建工具_Gradle:我们需要另一个构建工具吗?
  8. 通过粘性仙人掌基元进行延迟加载和缓存
  9. 使用精确的Java方法参数
  10. Java Bullshifier –生成大量随机代码库