为自定义屏障设计测试类
我必须使用锁作为我课程工作的一部分来实现自定义障碍类。 为了测试我的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而不是在外面声明它。