为自定义屏障设计测试类 [英] Designing a Test class for a custom Barrier

查看:129
本文介绍了为自定义屏障设计测试类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I had to do an implementation of a custom barrier class using locks as part of my course work. To test my LockBarrier class, I came up with a the following test code. It is working correctly, but I am concerned whether that is the proper way to do. Could you please suggest improvements I can do, especially structuring the classes. I think my way of coding is not the correct one. Any suggestions are welcome.

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 ,它应该在一个或多个线程上安排一些任务。您可以将负责线程的部分传递给调度程序,并测试调度程序是否正确地与此对象协作。这更像是经典的单元测试风格。

Testing stuff concurrently is hard (tm)! GOOS amongst other people recommending separating the concurrency part from the parts that are doing some work. So, for example, if you had some Scheduler which is supposed to schedule some task on one or more threads. You can pass in the part that is responsible for the threading to your scheduler and just test the scheduler collaborates with this object correctly. This is more in a classic unit testing style.

调度程序的示例是这里,这使用了一个模拟框架来提供帮助。如果您不熟悉这些想法,请不要担心,它们可能与您的测试无关。

An example with a `Scheduler is here, this uses a mocking framework to help. If you're not familiar with those ideas, don't worry, they're probably not relevant for your test.

话虽如此,您可能确实想要运行你的班级在上下文中以多线程的方式。这似乎是你上面写的那种测试。这里的诀窍是保持测试的确定性。好吧,我说,有几个选择。

Having said that, you might actually want to run your class 'in context' in a multi-threaded way. This seems to be the kind of test you're writing above. The trick here is to keep the test deterministic. Well, I say that, theres a couple of choices.

确定性

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

If you can setup your test to progress in a deterministic way, waiting at key points for conditions to be met before moving forward, you can try to simulate a specific condition to test. This means understanding exactly what you want to test (for example, forcing the code into a deadlock) and stepping through deterministically (for example, using abstractions like CountdownLatches etc to 'synchronise' the moving parts).

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

When you attempt to make some multi-threaded test syncrhonise its moving parts, you can use whatever concurrency abstraction is available to you but it's difficult because its concurrent; things could happen in an unexpected order. You're trying to mitegate this in your test by using the sleep calls. We generally don't like to sleep in a test because it'll make the test run slower and when you've got thousands of tests to run, every ms counts. If you lower the sleep period too much the test become non-deterministic and ordering isn't guaranteed.

一些例子包括

  • Forcing a deadlock using CountdownLatch
  • Setting up a thread to be interuptted

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

You've spotted one of the gotchas where the main test thread will finish before the newly spawned threads under test complete (using the join). Another way is to wait for a condition, for example using WaitFor.

浸泡/负载测试

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

Another choice is to setup a test to setup, run and spam your classes in an attempt to overload them and force them to betray some subtle concurrency issue. Here, just as in the other style, you'll need to setup up specific assertion so that you can tell if and when the classes did betray themselves.

对你来说然后重新测试,我建议你提出一个断言,这样你就可以看到对你的班级的正面和负面运行并替换 sleep (以及 system.out 调用。如果可以的话,从JUnit之类的东西运行你的测试会更加特殊。

For you're test then, I'd suggest coming up with an assertion so that you can see both positive and negative runs against your class and replacing the sleep (and system.out calls. If you can, running your test from something like JUnit is more idiosyncratic.

例如,您已经开始使用的样式的基本测试可能看起来像这样

For example, a basic test in the style you've started down might look like this

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));
    }
}

如果可能,向屏障添加超时是好的练习并帮助编写像这样的负面测试

If possible, adding a timeout to your Barrier is good practice and would help write a negative test like this

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))));
    }

}

如果你想发布你的实现,我们或许能够提出更多选择。希望能给你一些想法......

If you wanted to post your implementation, we might be able to suggest more alternatives. Hope that gives you some ideas though...

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

Another choice is to reach into your barrier object for finer grained assertions, for example,

@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/

这篇关于为自定义屏障设计测试类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆