Как проверить таймер?

Я хотел бы написать тест для метода, который вызывает наблюдателей в определенном интервалах, чтобы они выполняли метод. Таймер-объект работает в своем потоке.

Метод проверяемого таймера
private long waitTime;

public Metronome(int bpm) {
    this.bpm = bpm;
    this.waitTime = calculateWaitTime();
    this.running = false;
}

public void run() {
    long startTime = 0, estimatedTime = 0, threadSleepTime = 0;

    running = true;

    while (running) {
        startTime = System.nanoTime();

        tick();// notify observers here

        estimatedTime = System.nanoTime() - startTime;
        threadSleepTime = waitTime -estimatedTime;
        threadSleepTime = threadSleepTime < 0 ? 0 : threadSleepTime;

        try {
            Thread.sleep(threadSleepTime / 1000000l);

        } catch (InterruptedException e) {
                // sth went wrong
        }
    }
}
Фрагмент из моего тестового класса
private int ticks;
private long startTime;
private long stopTime;

@Test
public void tickTest(){
    metronome.setBpm(600);
    startTime = System.nanoTime();
    metronome.run();
    long duration = stopTime - startTime;
    long lowThreshold  =  800000000;
    long highThreshold =  900000000;
    System.out.println(duration);
    assertTrue(lowThreshold < duration); 
    assertTrue(duration <= highThreshold);      
}

@Override
public void update(Observable o, Object arg) {
    ticks ++;       
    if(ticks == 10){
        metronome.stop();
        stopTime = System.nanoTime();
    }
}

Сейчас мой тестовый класс регистрируется как наблюдатель на рассматриваемом объекте, так что я могу подсчитать количество раз, когда был выполнен пометка(). Тест измеряет время до и после казни, но мне кажется неудобным, чтобы проверить поведение таким образом.

Любые предложения по улучшению теста?

Ответ 1

Основываясь на ваших комментариях, я изменил свой код. Вместо того, чтобы реализовать интерфейс Observer в моем тестовом классе, теперь я создал частный класс, который реализует интерфейс регистров на моем таймере.

Спасибо за ваше время и мысли.

Вот как выглядит код:

измененный тестовый код
@Test(timeout = 2000)
public void tickTest(){     
    long lowThreshold  = 400000000;
    long highThreshold = 600000000;

    TickCounter counter = new TickCounter();
    metronome.addObserver(counter);
    metronome.setBpm(600);

    startTime = System.nanoTime();
    metronome.run();
    long duration = System.nanoTime() - startTime;


    assertTrue(lowThreshold <= duration);
    assertTrue(duration <= highThreshold);      
}

private class TickCounter implements Observer{
    private int ticks;

    public TickCounter(){
        ticks = 0;
    }

    @Override
    public void update(Observable o, Object arg) {
        ticks++;        
        if(ticks == 5){
            metronome.stop();
        }
    }       
}
фрагмент из моего измененного таймера
private long expectedTime; // calculated when bpm of timer is set

@Override
public void run() {
    long startTime = 0, elapsedTime = 0, threadSleepTime = 0;

    running = true;

    while (running) {
        startTime = System.nanoTime();

        tick();

        elapsedTime     = System.nanoTime() - startTime;

        threadSleepTime = expectedTime - elapsedTime;
        threadSleepTime = threadSleepTime < 0 ? 0 : threadSleepTime;

        try { TimeUnit.NANOSECONDS.sleep(threadSleepTime); } catch (Exception e) { }
    }
}

Моей самой большой проблемой могло быть то, что я реализовал интерфейс-наблюдатель в своем тестовом сценарии JUnit. Поэтому я создал частного наблюдателя, который специально подсчитывает количество раз, тик был выполнен. Затем счетчик останавливает мой таймер.

Тест-метод измеряет время и утверждает, что необходимое время находится где-то между моими определенными пределами.

Ответ 2

Иногда решение заключается в том, чтобы использовать что-то из стандартной библиотеки, которая достаточно проста, так что ее не нужно тестировать. Я думаю, что SchedulerExecuterService сделает трюк для замены домашнего таймера, который тестируется здесь. Обратите внимание, что довольно редко бывает, что ошибка в коде библиотеки, но они существуют.

В общем, я думаю, что нормально создавать класс-помощник или использовать насмешливую структуру (Mockito), чтобы сделать что-то простое, как подсчет "тиков".

P.S. Вы можете заменить Thread.sleep(threadSleepTime / 1000000l) на TimeUnit.NANOSECONDS.sleep(threadSleepTime)..., который перемещает некоторую логику из вашего кода в стандартную библиотеку.

Ответ 3

Это зависит от того, насколько точно вам нужно измерить время.

Если вы чувствуете, что это "неудобно", это потому, что вы не уверены, что измерение достаточно точное? Вы боитесь, что ОС мешает с накладными расходами?

Если это так, вам может понадобиться внешняя тайм-плата, синхронизированная с точным источником (GPS, атомный стандарт и т.д.) для проверки вашего кода или, возможно, для запуска триггера для вашего события стрельбы.

Ответ 4

Попробуйте это. Вам также нужно время, которое вы ожидаете. Ожидаемое время будет 1000000000/n, где n - количество раз, когда ваш таймер должен tick() в секунду.

public void run(){
    long time = System.nanotime();
    long elapsedTime = 0;
    // Hope you need to tick 30 times per second
    long expectedTime = 1000000000/30;
    long waitTime = 0;
    while (running){
        tick();
        elapsedTime = System.nanotime()-time;
        waitTime = expectedTime-elapsedTime();
        if (waitTime>0){
            try { Thread.sleep(waitTime) } catch (Exception e){}
        }
        time = System.nanotime();
    }
}