Christoph
Christoph

Reputation: 307

TDD: test wait functionality

I have a class Foo with something like a locking mechanism. Let's call those methods lock(), unlock() and waitUntilUnlocked(). The implementation of these methods is not of interest, since I want to test them (TDD).

Now I want to write a test testing that waitUntilUnlocked() really waits until unlock() is called. The idea I came up with is the following:

@Test
public void waitUntilUnlocked_waits() {
    final Foo foo = createFoo();
    final long[] durationInThread = new long[1];

    foo.lock();
    inThread(() -> {
        durationInThread[0] = measureDurationOf(this::sleepSomeTime);
        foo.unlock();
    });

    final long waitingTime = measureDurationOf(foo::waitUntilUnlocked);

    assertThat(waitingTime).isGreaterThanOrEqualTo(durationInThread[0]);
}

Drawbacks: This works with sleep() which makes it slow. Also, I get sometimes strange results that the assertion does not meet (The sleep time was 500ms, durationInThread[0] was 501ms and waitingTime was 498ms).

Do you have a better idea for a reliable and fast test, not open for race conditions?

Upvotes: 1

Views: 99

Answers (1)

UmNyobe
UmNyobe

Reputation: 22910

The idea is to write a test which will eventually fail if your locks were bogus, but there is no one single fast and reliable black-box test when it comes to synchronization.

Imagine somebody else, a freelancer, was writing waitUntilUnlocked(). He decides lazily to write it as

waitUntilUnlocked()
{
    //haha I know they cannot lock for more than 10 sec, easy money
    Thread.sleep(10*1000);
}

Your test will pass. You are trying to test that the caller of waitUntilUnlocked() will wait forever, which cannot be tested.

A simpler way to test that it is the combination of these two tests.

The first one is your test a bit simplified. It tests the thread wait indeed at least some time when locked.

final Foo foo = createFoo();

foo.lock();
inThread(() -> {
        foo.waitUntilUnlocked();
    });
long time_millisec = 100;
inThread.join(time_millisec);
bool success = inThread.isAlive(); 

It never unlocks. time_millisec is your parameter. No need to measure times.

The second one test that the thread make progress if unlocked. You could add a timer to measure the time before join completes.

final Foo foo = createFoo();

inThread(() -> {
        foo.waitUntilUnlocked();
    });
inThread.join();
return true; //success

You will execute anything after join only after waitUntilUnlocked has terminated.

Black box testing shows its limits in your situation

Upvotes: 1

Related Questions