11

I would like to write a test for a method, that calls observers in a specific intervall, so that they will execute a method. The timer-object runs in its own thread.

Method of timer to be tested
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
        }
    }
}
Snippet from my testclass
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();
    }
}

Right now, my testclass registers as an observer at the object in question, so that i can count the number of times tick() was executed. The test measures the time before and after the execution, but it feels awkward to me, to test the behaviour this way.

Any suggestions for improving the test?

ccaspers
  • 131
  • 1
  • 7
  • That is no answer to my question, except you are trying to tell me that my test is fine. – ccaspers Sep 24 '12 at 22:50
  • @John: Thanks for backing me up on this one ;) – ccaspers Sep 24 '12 at 22:51
  • Well, that's why it's not posted as an answer, basically I'm saying your test case is fine. I'm sorry if you're not a novice, it just seems an odd thing for a more advanced user to ask, don't know what John is getting at, the fact that computer science is not always intuative is the entire reason this site does exist. – awiebe Sep 24 '12 at 22:56
  • Alright, I get what you mean. I will leave this question open till I get up tomorrow morning. If nothing more comes up I'll mark it as answered :) – ccaspers Sep 24 '12 at 23:05
  • Most of the comments have gone quite off topic. We all have bad days (myself included), maybe some people commenting are stressed. No point in doubling down, just move on. – Tim Bender Sep 24 '12 at 23:17
  • @TimBender I deleted my comments. I wasn't doubling down, by the way. The other user didn't know where I was coming from, so I explained. No hard feelings. :) – John Sep 25 '12 at 18:30

4 Answers4

2

Sometimes the solution is to use something from a standard library that is sufficiently simple such that it does not need to be tested. I think SchedulerExecuterService will do the trick for replacing the home made Timer being tested here. Note that it is pretty rare to be bit by a bug in library code, but they do exist.

In general though, I think it is okay to create a helper class or use a mocking framework (Mockito) to do something simple like counting "ticks".

P.S. You can replace Thread.sleep(threadSleepTime / 1000000l) with TimeUnit.NANOSECONDS.sleep(threadSleepTime) ... which moves some logic from your code into the standard library.

Tim Bender
  • 20,112
  • 2
  • 49
  • 58
2

Based on your comments I changed my code. Instead of implementing the Observer-interface in my testclass, I now created a private class, that implements the interface an registers at my timer.

Thanks for your time and thoughts.

Here is what the code now looks like:

revised testcode
@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();
        }
    }       
}
snippet from my revised timer
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) { }
    }
}

My biggest issue might have been, that I implemented the observer-interface in my JUnit testcase. So I created a private observer, that specifically counts the number of times, the tick was executed. The counter then stops my timer.

The testmethod measures the timing and asserts, that the needed time is somewhere between my defined limits.

ccaspers
  • 131
  • 1
  • 7
  • Looks good. If this is your answer (and you don't find any other more appropriate) then you should accept it to close out the question. – Tim Bender Sep 25 '12 at 17:56
  • I will do that, as soon as im allowed to ;) I still have to wait 14 hours before I can accept my own answer as answer to the question – ccaspers Sep 26 '12 at 08:03
1

It depends on how accurately you need to measure the time.

If you feel that it's "awkward" is that because you're not sure that the measurement is accurate enough? Do you fear that the OS is getting in the way with overhead?

If so, you may need an external timing board that's synchronized to an accurate source (GPS, atomic standard, etc.) to either test your code, or possibly to provide the trigger for your firing event.

John
  • 15,990
  • 10
  • 70
  • 110
  • In that case, the operating systems accuracy has to suffice. The timer is used to play one or later maybe multiple sounds via midi or mp3. Actually it is not the accuracy that bothers me, but the way I am testing it. – ccaspers Sep 24 '12 at 22:55
  • OK, then this is overkill. :) Actually I think what threw me was that I saw "1000000l" and thought you were writing "ten million one" not "one million followed by a lowercase l" -- hence I thought you were looking for extremely accurate timing. – John Sep 24 '12 at 22:56
1

Try this. You also need the time you are expecting. The expected time will be 1000000000/n where n is the number of times your timer needs to tick() per second.

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();
    }
}
Sri Harsha Chilakapati
  • 11,744
  • 6
  • 50
  • 91
  • Actually, my waitTime is your expectedTime. I fixed the naming of my variables to improve readability. – ccaspers Sep 25 '12 at 10:03