Java asyncron方法的JUnit测试

时间:2014-07-24 19:09:53

标签: java junit

今天我必须编写一个方法,将一个String作为参数,创建一个新线程并在等待5秒后将其写入consol,所以像这样:

public void exampleMethod(final String str){

    Runnable myRunnable = new Runnable(){    
        public void run(){
            try {
                Thread.sleep(5000);
                System.out.println(str);
            } catch (InterruptedException e) {                      
                //handling of the exception
            }                   
        }
    };

    Thread thread = new Thread(myRunnable);
    thread.start();
    //some other things to do 
}

我的问题是:我如何测试以及我应该在这里用JUnit测试什么?

谢谢!

5 个答案:

答案 0 :(得分:0)

这种方法没有什么复杂的。您只使用标准API方法:Thread.sleepSystem.out.println,...

参数刚打印出来,您不需要对其进行修改,也不能将其用于计算或其他方法。

对于您自己编写的代码,只有STL没有副作用。

并且没有可以测试的方法的结果。

在我看来,没有必要而且不可能简单地测试它。 你可以测试的唯一的东西(即使这不会是微不足道的),如果在一段时间后打印String

答案 1 :(得分:0)

  

[...] JUnit在线程仍处于活动状态时完成执行。在线程的执行结束时可能存在问题,但是你的测试永远不会反映出来。
  问题在于JUnit的TestRunner。它不是设计用于查找Runnable实例并等待报告其活动。它将它们解雇并忘记它们。出于这个原因,JUnit中的多线程单元测试几乎不可能编写和维护。

嗯,来源 - this article - 来自2003年,并且无法保证这还没有修复,但您可以自己尝试一下。

我的建议是:
运行代码并测量所需的时间。然后在启动异步任务后添加大约1000毫秒,然后添加Thread.sleep(executionTime+1000);。不那么优雅,但应该在实践中工作。如果你想在这里更优雅(浪费更少的时间),你可能想要寻找提供解决方案的框架 ...或者,如果您在测试中直接启动Thread,也可以使用Thread.join等待,但是您将遇到无法执行此操作的情况。

修改
还要检查this article,它可以提供解决方案将这些错误传递给主线程:

public class AsynchTester{
    private Thread thread;
    private volatile Error error;
    private volatile RuntimeException runtimeExc;

    public AsynchTester(final Runnable runnable) {
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    runnable.run();
                } catch (Error e) {
                    error = e;
                } catch (RuntimeException e) {
                    runtimeExc = e;
                }
            }
        });
    }

    public void start() {
        thread.start();
    }

    public void test() throws InterruptedException {
        thread.join();
        if (error != null)
            throw error;
        if (runtimeExc != null)
            throw runtimeExc;
    }
} 

使用它:

@Test
public void test() throws InterruptedException {
    AsynchTester tester = new AsynchTester(new Runnable() {
       @Override
       public void run() {
           //async code
        }
    });
    tester.start(); 
    tester.test();
}

答案 2 :(得分:0)

这里的问题是您尝试测试交互而不是简单的返回结果或状态更改。但是,这并不意味着无法完成。

标准输出PrintStream可以替换为System.setOut()。您可以注入自己的模拟实现,以便验证String是否已写入流。您必须要小心,因为这会更改全局状态,它可能会影响依赖于标准输出的其他代码或测试。至少,您必须放回原始流。但如果测试并行运行,事情可能会变得更复杂。

这将我们带到下一个问题,即睡眠。没有强有力的保证睡眠会阻止多长时间。这意味着您的测试必须提供一些缓冲区以确保线程有时间在检查模拟流的状态之前编写String。由于某些执行时序抖动,您不希望您的测试变得不稳定。所以你必须决定你认为可接受的缓冲区。


另一种方法是更改​​代码的实现,以便更容易测试。

执行此操作的最简单方法是删除所有静态依赖项。可以使用System.out来初始化类,而不是显式引用PrintStream。此外,您可以定义一个包装Thread.sleep()的接口。出于测试目的,您可以使用模拟流和睡眠界面的无操作实现来初始化类。但是,您可能仍然会遇到一些时序问题,因为在继续测试之前需要执行新创建的线程。


你可以做的另一件事是退后一步,决定你对这个被测试代码的关注程度。此示例中只有4行有趣的代码,但没有一行是复杂的。进行代码审查可能足以确保没有错误。

但是,如果业务逻辑比写入标准更复杂,您可能会认为测试很重要。好消息是,在执行程序中安排任务是直截了当的,这也是使测试变得困难的部分。您可以创建一个包含后台线程中任务调度的抽象。然后为自己提供更直接的业务逻辑访问,以便对其进行测试。

答案 3 :(得分:0)

我经常通过提供一个ResultTarget来解决这个问题,它为线程实现了一个接口IResultTarget, 在生产代码中,结果将是包含计算结果的列表。 (或null)

在单元测试中,ResultTarget是单元测试类本身,然后可以轻松检查收到的结果。

public Interface IResultTarget {
  List getResult();
}

public void ThreadTest extends TestCase implements IResultTarget {

 List result;

 public List getResult(
   return this.result;
 }

 public void testThread() {
   MyRunnable myRunnable= new MyRunnable ();
   myRunnable.setResultTarget(this);
   Thread thread = new Thread(myRunnable);
   thread .start();

   Thread.sleep(5 * 1000);
   // expecting one element as result of the work of myRunnable.
   assertEquals(1, result.size());
 }
}

答案 4 :(得分:0)

要对要执行断言的单独线程进行任何有用的测试,可以查看ConcurrentUnit。例如:

@Test
public void shouldWaitForResume() throws Throwable {
  final Waiter waiter = new Waiter();

  // Start worker thread that performs an assertion after some delay, then resumes the waiter
  new Thread(new Runnable() {
    public void run() {
      doSomeWork();
      waiter.assertTrue(true);
      waiter.resume();
    }
  }).start();

  // Waits for resume to be called
  waiter.await(1000);
}