执行摘要:当线程中抛出断言错误时,单元测试不会消失.这是有道理的,因为不应该允许一个线程崩溃另一个线程.问题是我如何1)在第一个辅助线程崩溃时使整个测试失败,或者2)循环并确定每个线程完成后的状态(参见下面的代码).执行后者的一种方法是通过具有每线程状态变量,例如"boolean [] status"并且具有"status [i] == false"意味着线程失败(这可以被扩展以捕获更多信息).但是,这不是我想要的:我希望它在抛出断言错误时就像任何其他单元测试一样失败.这甚至可能吗?这是可取的吗?
我感到无聊,我决定在我的单元测试中产生一堆线程,然后让它们调用一个服务方法,只是为了它.代码看起来大致如下:
Thread[] threads = new Thread[MAX_THREADS]; for( int i = 0; i < threads.length; i++ ) { threads[i] = new Thread( new Runnable() { private final int ID = threadIdSequenceNumber++; public void run() { try { resultRefs[ID] = runTest( Integer.toString( ID ) ); // returns an object } catch( Throwable t ) { // this code is EVIL - it catches even // Errors - don't copy it - more on this below final String message = "error testing thread with id => " + ID; logger.debug( message, t ); throw new IllegalStateException( message, t ); // need to wrap throwable in a // run time exception so it will compile } } } ); }
在此之后,我们将遍历线程数组并启动每个线程.之后我们将等待他们全部完成.最后,我们将对结果引用执行一些检查.
for( Thread thread : threads ) thread.start(); logger.debug( "waiting for threads to finish ..." ); boolean done = false; while( !done ) { done = true; for( Thread thread : threads ) if( thread.isAlive() ) done = false; } for( int i = 0; i < resultRefs.length; i++ ) { assertTrue( "you've got the world messed, dawg!", myCondition(resultRefs[i]) );
这是问题所在.你有没有注意到那个讨厌的try-catch-throwable块?我刚刚补充说,作为临时黑客,所以我可以看到发生了什么.在runTest(String)中,会产生一些断言,例如assertNotNull(null),但由于它位于不同的线程中,因此不会导致单元测试失败!
我的猜测是,我们需要以某种方式迭代线程数组,检查每个数据的状态,并在线程以令人讨厌的方式终止时手动导致断言错误.提供此信息的方法的名称是什么(死线程的堆栈跟踪).
并发性是单元测试非常困难的事情之一.如果您只是想测试每个线程中的代码是否正在执行它应该测试的内容,那么您可能应该只测试上下文中隔离的代码.如果在此示例中线程协作以达到结果,则可能是您可以在不使用线程的情况下测试该协作.这将通过顺序执行所有协作部分来完成.如果你想测试竞争条件和这些东西,单元测试不是最好的方法.您将获得有时会失败但有时不会失败的测试.总而言之,我认为可能是你的问题是你的单元测试水平太高了.希望这可以帮助
谷歌测试博客有一篇关于这个主题的优秀文章非常值得一读:http://googletesting.blogspot.com/2008/08/tott-sleeping-synchronization.html
它是用Python编写的,但我认为这些原则可以直接转移到Java.