11

我想为一个方法编写一个测试,以特定的时间间隔调用观察者,以便他们执行一个方法。计时器对象在其自己的线程中运行。

待测定时器方法
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();
    }
}

现在,我的测试类注册为相关对象的观察者,这样我就可以计算执行 tick() 的次数。测试测量执行前后的时间,但我感觉很尴尬,以这种方式测试行为。

有什么改进测试的建议吗?

4

4 回答 4

2

有时解决方案是使用标准库中足够简单的东西,以至于不需要对其进行测试。我认为SchedulerExecuterService可以替换这里正在测试的自制计时器。请注意,很少会被库代码中的错误所困扰,但它们确实存在。

不过总的来说,我认为创建一个辅助类或使用一个模拟框架(Mockito)来做一些简单的事情,比如计算“滴答声”是可以的。

PS您可以替换Thread.sleep(threadSleepTime / 1000000l)TimeUnit.NANOSECONDS.sleep(threadSleepTime)... ,它将一些逻辑从您的代码移动到标准库中。

于 2012-09-24T23:03:18.023 回答
2

根据您的评论,我更改了代码。我现在没有在我的测试类中实现观察者接口,而是创建了一个私有类,它实现了在我的计时器上注册的接口。

感谢您的时间和想法。

下面是代码现在的样子:

修改后的测试代码
@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 测试用例中实现了观察者接口。所以我创建了一个私人观察者,专门计算滴答被执行的次数。然后计数器停止我的计时器。

测试方法测量时间并断言所需时间在我定义的限制之间。

于 2012-09-25T10:15:14.660 回答
1

这取决于您需要多准确地测量时间。

如果您觉得它“尴尬”是因为您不确定测量是否足够准确?您是否担心操作系统会阻碍开销?

如果是这样,您可能需要一个与准确源(GPS、原子标准等)同步的外部计时板来测试您的代码,或者可能为您的触发事件提供触发器。

于 2012-09-24T22:48:13.143 回答
1

试试这个。你还需要你期待的时间。预期时间将是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();
    }
}
于 2012-09-25T00:36:26.310 回答