为自定义屏障设计测试类

我必须使用锁作为我课程工作的一部分来实现自定义障碍类。 为了测试我的LockBarrier类,我提出了以下测试代码。 它工作正常,但我担心这是否是正确的方法。 你能否提出我可以做的改进,特别是构建课程。 我认为我的编码方式不正确。 欢迎任何建议。

 public class TestDriver { private static LockBarrier barrier; static class Runnable1 implements Runnable { public Runnable1() { } public void run() { try { System.out.println(Thread.currentThread().getId()+" lazy arrived at barrier"); Thread.sleep(10000); barrier.await(); System.out.println(Thread.currentThread().getId()+" passed barrier"); } catch (InterruptedException ie) { System.out.println(ie); } } } static class Runnable2 implements Runnable { public Runnable2() { } public void run() { try { System.out.println(Thread.currentThread().getId()+" quick arrived at barrier"); //barrier.await(1,TimeUnit.SECONDS); barrier.await(); System.out.println(Thread.currentThread().getId()+" passed barrier"); } catch (InterruptedException ie) { System.out.println(ie); } } } static class Runnable3 implements Runnable { public Runnable3() { } public void run() { try { System.out.println(Thread.currentThread().getId()+" very lazy arrived at barrier"); Thread.sleep(20000); barrier.await(); System.out.println(Thread.currentThread().getId()+" passed barrier"); } catch (InterruptedException ie) { System.out.println(ie); } } } public static void main(String[] args) throws InterruptedException { barrier = new LockBarrier(3); Thread t1 = new Thread(new TestDriver.Runnable1()); Thread t2 = new Thread(new TestDriver.Runnable2()); Thread t3 = new Thread(new TestDriver.Runnable3()); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); } } 

为您的类分离并发

同时测试内容很难(tm)! GOOS和其他人建议将并发部分与正在进行某些工作的部分分开。 因此,例如,如果您有一些Scheduler ,它应该在一个或多个线程上安排一些任务。 您可以将负责线程的部分传递给调度程序,并测试调度程序是否正确地与此对象协作。 这更像是经典的unit testing风格。

这里有一个`Scheduler的例子,它使用一个模拟框架来帮助。 如果您不熟悉这些想法,请不要担心,它们可能与您的测试无关。

话虽如此,您实际上可能希望以multithreading方式在“上下文”中运行您的类。 这似乎是你上面写的那种测试。 这里的诀窍是保持测试的确定性。 嗯,我说,有几个选择。

确定性

如果您可以设置测试以确定的方式进行,在关键点等待条件满足,然后再继续,您可以尝试模拟要测试的特定条件。 这意味着准确理解您要测试的内容(例如,强制代码进入死锁)并逐步确定(例如,使用CountdownLatches等抽象来’同步’移动部件)。

当你试图让一些multithreading测试同步它的移动部分时,你可以使用任何可用的并发抽象,但它很难因为它的并发; 事情可能以意想不到的顺序发生。 你试图通过使用sleep调用来测试你的测试。 我们通常不喜欢在测试中睡觉,因为它会使测试运行得更慢,并且当您需要运行数千个测试时,每个ms都会计数。 如果您将睡眠周期降低太多,则测试变得不确定,并且无法保证排序。

一些例子包括

  • 使用CountdownLatch 强制死锁
  • 设置要插入的线程

您已经发现主要测试线程将在测试完成的新生成的线程(使用join )之前完成的其中一个陷阱。 另一种方法是等待条件,例如使用WaitFor 。

浸泡/负载测试

另一个选择是设置一个测试来设置,运行和垃圾邮件你的类,试图超载它们并强迫它们背叛一些微妙的并发问题。 在这里,就像在另一种风格中一样,你需要设置特定的断言,以便你可以判断这些类是否以及何时背叛自己。

因为你正在测试,我建议你提出一个断言,这样你就可以看到你的类的正面和负面运行并替换sleep (和system.out调用。如果可以的话,从类似的东西运行你的测试JUnit更具特异性。

例如,您开始使用的样式的基本测试可能如下所示

 public class TestDriver { private static final CyclicBarrier barrier = new CyclicBarrier(3); private static final AtomicInteger counter = new AtomicInteger(0); static class Runnable1 implements Runnable { public void run() { try { barrier.await(); counter.getAndIncrement(); } catch (Exception ie) { throw new RuntimeException(); } } } @Test (timeout = 200) public void shouldContinueAfterBarrier() throws InterruptedException { Thread t1 = new Thread(new Runnable1()); Thread t2 = new Thread(new Runnable1()); Thread t3 = new Thread(new Runnable1()); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); assertThat(counter.get(), is(3)); } } 

如果可能的话,在屏障上添加超时是一种很好的做法,并且有助于编写这样的负面测试

 public class TestDriver { private static final CyclicBarrier barrier = new CyclicBarrier(3); private static final AtomicInteger counter = new AtomicInteger(0); static class Runnable1 implements Runnable { public void run() { try { barrier.await(10, MILLISECONDS); counter.getAndIncrement(); } catch (Exception ie) { throw new RuntimeException(); } } } @Test (timeout = 200) public void shouldTimeoutIfLastBarrierNotReached() throws InterruptedException { Thread t1 = new Thread(new Runnable1()); Thread t2 = new Thread(new Runnable1()); t1.start(); t2.start(); t1.join(); t2.join(); assertThat(counter.get(), is(not((3)))); } } 

如果您想发布您的实施,我们可能会建议更多的替代方案。 希望能给你一些想法……

编辑:另一个选择是进入你的屏障对象,以获得更精细的断言,例如,

 @Test (timeout = 200) public void shouldContinueAfterBarrier() throws InterruptedException, TimeoutException { Thread t1 = new Thread(new BarrierThread(barrier)); Thread t2 = new Thread(new BarrierThread(barrier)); Thread t3 = new Thread(new BarrierThread(barrier)); assertThat(barrier.getNumberWaiting(), is(0)); t1.start(); t2.start(); waitForBarrier(2); t3.start(); waitForBarrier(0); } private static void waitForBarrier(final int barrierCount) throws InterruptedException, TimeoutException { waitOrTimeout(new Condition() { @Override public boolean isSatisfied() { return barrier.getNumberWaiting() == barrierCount; } }, timeout(millis(500))); } 

编辑:我在http://tempusfugitlibrary.org/recipes/2012/05/20/testing-concurrent-code/上写了一些内容。

代码对我来说很好看。 也许你可以将LockBarrier传递给Runnable而不是在外面声明它。