Hilikus
Hilikus

Reputation: 10331

how to unit test this threaded code

I have a class that normally runs in a thread that processes data forever until another thread invokes stop() on it. The problem I have is that the unit test gets stuck in the main loop since the test is single threaded and I want to keep it that way. How can I unit test this without polluting the code? this class is part of a critical system and needs to be as simple and efficient as possible so I want to avoid unit testing hacks in the code

public class MyClass implements Runnable {

   boolean running;

   public void run() {
      //foo is injected from the outside
      foo.start();
      work();
      foo.end();
   }

   public void work() { 
      running = true;
      while(running) { //main loop
         bar.process(); //bar is injected from the outside
      }
   }

   public void stop() {
      running = false;
   }
}

Basically what I'm doing in the test is mocking out foo and bar and I call run() from the unit test, where later I verify in the bar mock whether process was actually called. I also verify that in the foo mock start() and end() got called. The problem is that because I really want to keep the test single threaded, the test thread gets stuck forever in the while(running) loop.

Some things I have tried and don't like

Are there any other suggestions or common patterns to test Runnables, or maybe a better way to write my code so that testing it is easier?

Upvotes: 0

Views: 2201

Answers (2)

ThomasD
ThomasD

Reputation: 131

Create an interface for the class of bar that only contains the method process. Your MyClass seems generic enough so that this would be OK. Then, instead of mocking bar, create your own implementation dummy (or mock, whatever you like to call it). This will then call the stop method and your process method is only called once. You can check whether BarMock.process was called with an assertion using its isCalled method. Also, I would suggest an isRunning method for your MyClass so that you can check whether it was stopped.

public interface Processable {
    public void process();
}

public class BarMock implements Processable {
    private MyClass clazz;
    private boolean called;

    public BarMock(MyClass clazz) {
        this.clazz = clazz;
        called = false;
    }

    @Override
    public void process() {
        // you can assertTrue(clazz.isRunning()) here, if required
        called = true;
        clazz.stop();
    }

    public boolean isCalled() {
        return called;
    }
}

public class MyClass implements Runnable {

    boolean running;

    public void run() {
        // foo is injected from the outside
        foo.start();
        work();
        foo.end();
    }

    public void work() {
        running = true;
        while (running) { // main loop
            bar.process(); // bar is injected from the outside
        }
    }

    public void stop() {
        running = false;
    }

    public boolean isRunning() {
        return running;
    }
}

I think this method has three advantages over the one suggested by William F. Jameson, but also disadvantages:

Advantages:

  • You can test whether your process method was actually called
  • You don't have to add code that you never use during the actual program run
  • You can test whether the stop method really stops

Disadvantages:

  • You have to introduce an interface
  • Need to test BarMock class, too

That said, I'd still prefer introducting the interface, since it doesn't pollute your code too much and therefore is a small price to pay.

Upvotes: 1

William F. Jameson
William F. Jameson

Reputation: 1843

I suggest making a change which would both make your code more by-the-book and allow breaking out in a single thread:

while (!Thread.currentThread().isInterrupted() && running) {
     bar.process();
  }

You can call Thread.currentThread().interrupt() before you run this code; the thread's interrupted flag will be set and the method isInterrupted() will return true.

This is more by-the-book because it makes your main loop participate in Java's interruption mechanism.

Upvotes: 5

Related Questions