深圳罗湖企业网站建设报价,济南网站建设的公司,昆明网站开发多少钱,寿光网站开发测试线程非常困难#xff0c;这使得为要测试的多线程系统编写良好的集成测试非常困难。 这是因为在JUnit中#xff0c;测试代码#xff0c;被测对象和任何线程之间没有内置的同步。 这意味着#xff0c;当您必须为创建并运行线程的方法编写测试时#xff0c;通常会出现问题… 测试线程非常困难这使得为要测试的多线程系统编写良好的集成测试非常困难。 这是因为在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