如何对这个线程代码进行单元测试

时间:2014-08-02 18:28:56

标签: java multithreading unit-testing mockito

我有一个类通常在一个线程中运行,该线程永远处理数据,直到另一个线程对它调用stop()。我遇到的问题是单元测试卡在主循环中,因为测试是单线程的,我想保持这种方式。如何在不污染代码的情况下对其进行单元测试?这个类是关键系统的一部分,需要尽可能简单有效,所以我想避免代码中的单元测试黑客

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;
   }
}

基本上我在测试中做的是嘲笑 foo bar ,我从单元测试中调用run(),后来我验证了 bar 模拟是否实际调用了process。我还验证了在 foo 模拟start()end()被调用。问题是因为我真的想保持测试单线程,测试线程永远停留在while(running)循环中。

我尝试过但不喜欢的一些事情

  • 添加一些VM属性以在主循环迭代结束时触发中断。这样做的问题在于,如上所述,这段代码非常关键,我希望保持代码不受单元测试混乱的影响。我不希望生成代码在每次迭代中评估一些我在开发时仅使用的VM属性
  • 使用 bar 模拟在调用process()时调用stop()。 Mockito不喜欢我调用另一个类'方法并抛出异常的事实
  • 外化主循环的控制。所以我没有检查while中的布尔值,而是调用一个返回是否继续的方法。并且这个循环控制对象可以从外部注入,这样在单元测试中我可以使控制方法返回true然后返回false以从循环中获得单个迭代。这会使代码变得非常复杂,使得它不自然且难以阅读,而且只在单元测试环境中才有意义

是否有任何其他建议或常用模式来测试Runnables,或者更好的方法来编写我的代码以便更容易测试?

2 个答案:

答案 0 :(得分:5)

我建议进行一项更改,这将使您的代码更多地通过书籍允许在单个帖子中突破:

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

您可以在运行此代码之前调用Thread.currentThread().interrupt();线程的interrupted标志将被设置,方法isInterrupted()将返回true。

这更像是一本书,因为它使你的主循环参与了Java的中断机制。

答案 1 :(得分:1)

为仅包含方法bar的{​​{1}}类创建一个接口。你的process似乎足够通用,这样就行了。然后,不是模拟MyClass,而是创建自己的实现虚拟(或模拟,无论你喜欢称之为什么)。然后,这将调用stop方法,并且只调用一次bar方法。您可以使用process方法检查是否使用断言调用了BarMock.process。另外,我建议您isCalled使用isRunning方法,以便检查是否已停止。

MyClass

我认为这种方法比William F. Jameson提出的方法有三个优点,但也有缺点:

优点:

  • 您可以测试是否实际调用了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; } } 方法
  • 您不必添加在实际程序运行期间从未使用的代码
  • 您可以测试process方法是否真的停止

缺点:

  • 您必须介绍一个界面
  • 也需要测试BarMock类

尽管如此,我仍然更喜欢介绍界面,因为它不会过多地污染你的代码,因此付出的代价很小。