Проектирование класса испытаний для пользовательского барьера

Мне пришлось выполнить реализацию пользовательского класса барьера, используя блокировки, как часть моей курсовой работы. Чтобы проверить класс 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();
        }   
} 

Ответ 1

Разделение Concurrency для вашего класса

Тестирование материала одновременно затруднено (tm)! GOOS среди других людей, рекомендующих отделить часть Concurrency от частей, которые выполняют определенную работу. Например, если у вас есть Scheduler, который должен планировать какую-либо задачу на одном или нескольких потоках. Вы можете передать часть, которая отвечает за потоки для вашего планировщика, и просто проверить, что планировщик правильно работает с этим объектом. Это больше похоже на классический стиль тестирования единицы.

Пример с "Планировщик" здесь, это использует насмешливую структуру, чтобы помочь. Если вы не знакомы с этими идеями, не волнуйтесь, они, вероятно, не подходят для вашего теста.

Сказав это, вы, возможно, захотите запустить свой класс "в контексте" многопоточным способом. Кажется, это тот тест, который вы пишете выше. Трюк здесь состоит в том, чтобы держать тест детерминированным. Ну, я говорю, что это пара вариантов.

Детерминированный

Если вы можете настроить ваш тест на прогресс детерминированным способом, ожидая в ключевых точках для условий, которые должны быть выполнены перед движением вперед, вы можете попытаться смоделировать определенное условие для проверки. Это означает понимание того, что вы хотите протестировать (например, заставляя код заходить в тупик) и детерминистически пройти (например, используя абстракции, такие как CountdownLatches и т.д., Чтобы "синхронизировать" движущиеся части).

При попытке сделать несколько многопоточных тестов синхронизируйте свои движущиеся части, вы можете использовать любую абстракцию Concurrency, доступную вам, но это сложно, потому что ее одновременный; все может случиться в неожиданном порядке. Вы пытаетесь сделать это в своем тесте, используя вызовы sleep. Обычно нам не нравится спать в тесте, потому что это заставит тест работать медленнее, и когда у вас есть тысячи тестов для запуска, каждый мс подсчитывает. Если вы слишком сильно уменьшаете период сна, тест становится недетерминированным, и заказ не гарантируется.

Некоторые примеры включают

Вы заметили одну из ошибок, когда основной тестовый поток завершится до того, как завершатся новые порожденные потоки (используя join). Другой способ - подождать условия, например, используя WaitFor.

Тестирование на выдержку/нагрузку

Другой выбор - настроить тест для настройки, запуска и спама ваших классов в попытке перегрузить их и заставить их предать некоторую тонкую проблему Concurrency. Здесь, как и в другом стиле, вам нужно настроить конкретное утверждение, чтобы вы могли определить, действительно ли и когда классы предали себя.

Итак, для вас тест, я бы предложил придумать утверждение, чтобы вы могли видеть как положительные, так и отрицательные прогоны против вашего класса и заменять вызовы sleepsystem.out. ваш тест от чего-то вроде JUnit более похож на идиосинкразированный.

Например, тест basic в стиле, который вы начали, может выглядеть так:

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

EDIT: я написал некоторые из них на http://tempusfugitlibrary.org/recipes/2012/05/20/testing-concurrent-code/

Ответ 2

Код выглядит хорошо для меня. Возможно, вы можете передать LockBarrier в Runnable, а не объявлять его снаружи.