Разработка тестового класса для пользовательского барьера

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

2 ответа

Разделение параллелизма для вашего класса

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

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

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

детерминистический

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

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

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

Вы заметили одну из ошибок, где основной тестовый поток завершит работу до того, как новые тестируемые потоки будут завершены (используя 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 вместо того, чтобы объявить его снаружи.

Другие вопросы по тегам