如何使用线程执行单元测试? [复制]

Posted

技术标签:

【中文标题】如何使用线程执行单元测试? [复制]【英文标题】:How do I perform a Unit Test using threads? [duplicate] 【发布时间】:2010-09-23 19:06:06 【问题描述】:

执行摘要:当在线程中抛出断言错误时,单元测试不会死。这是有道理的,因为不应允许一个线程使另一个线程崩溃。问题是我如何 1) 在第一个辅助线程崩溃时使整个测试失败,或者 2) 在每个线程都完成后循环并确定每个线程的状态(参见下面的代码)。实现后者的一种方法是使用每个线程的状态变量,例如,“boolean[] statuses”和“statuses[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),但由于它在不同的线程中,它不会导致单元测试失败!!!!

我的猜测是,我们需要以某种方式遍历线程数组,检查每个线程的状态,如果线程以令人讨厌的方式终止,则手动导致断言错误。提供此信息的方法的名称是什么(死线程的堆栈跟踪)。

【问题讨论】:

【参考方案1】:

并发是很难进行单元测试的事情之一。如果您只是想测试每个线程内的代码是否正在执行它应该测试的内容,那么您可能应该只测试与上下文隔离的代码。 如果在此示例中线程协作以达到结果,您可能可以在不使用线程的情况下测试该协作。这将通过按顺序执行所有协作部分来完成。 如果你想测试竞争条件和这类事情,单元测试不是最好的方法。你会得到有时会失败,有时不会失败的测试。 总而言之,我认为您的问题可能是您的单元测试级别太高了。 希望这会有所帮助

【讨论】:

好点。出于好奇,我主要是对编写测试感兴趣。我看到这不是一件微不足道的事情。 :( 有趣的是,编程正在同时推动更多的多线程和更多的单元测试。这两个目标似乎是不相容的,除非我们转向减少并发问题的纯函数式语言。..【参考方案2】:

Google 测试博客有一篇关于这个主题的优秀文章值得一读:http://googletesting.blogspot.com/2008/08/tott-sleeping-synchronization.html

它是用 Python 编写的,但我认为这些原理可以直接转移到 Java 中。

【讨论】:

太棒了!我第一次遇到 python threading.Event。它应该解决了我对多线程代码进行单元测试的问题。【参考方案3】:

多线程环境中的单元测试很困难……因此需要进行一些调整。单元测试必须是可重复的……确定性的。结果,任何具有多个线程的东西都不符合这个标准。多线程测试也往往很慢。

我会尝试看看我是否可以通过在单个线程上进行测试。被测试的逻辑是否真的需要多个线程。 如果这不起作用,请使用成员变量方法,您可以在测试结束时检查预期值,此时所有线程都已完成运行。

嘿,好像还有一个这样的问题。查看我的帖子以获取 tdd yahoogroup 上更长讨论的链接 Unit testing a multithreaded application?

【讨论】:

【参考方案4】:

您的可运行包装器应该将异常对象传递回您的测试类,然后您可以将它们存储在一个集合中。完成所有测试后,您可以测试该集合。如果它不为空,则遍历每个异常和 .printStackTrace() 然后失败。

【讨论】:

【参考方案5】:

实现一个 UncaughtExceptionHandler 设置一些标志(线程定期检查)和set it on each Thread。

【讨论】:

【参考方案6】:

Junit 并发线程测试的另一个流行选项是 Matthieu Carbou 使用自定义 JunitRunner 和简单注释的方法。

See the full documentation

【讨论】:

【参考方案7】:

使用特殊的同步对象有可能使单元测试失败。看看下面的文章: Sprinkler - Advanced synchronization object

我将尝试解释这里的要点。 您希望能够将内部线程故障外部化到主线程,在您的情况下是测试。因此,您必须使用内部线程和测试都将用来相互同步的共享对象/锁。 请参阅以下测试 - 它创建一个线程,通过调用名为 Sprinkler 的共享对象来模拟抛出的异常。 主线程(测试)在 Sprinkler.getInstance().await(CONTEXT, 10000) 上被阻塞 在调用 release 时,它​​会释放并捕获抛出的异常。 在 catch 块中,您可以编写未通过测试的断言。

 @Test
    public void testAwait_InnerThreadExternalizeException() 

        final int CONTEXT = 1;
        final String EXCEPTION_MESSAGE = "test inner thread exception message";

        // release will occur sometime in the future - simulate exception in the releaser thread
        ExecutorServiceFactory.getCachedThreadPoolExecutor().submit(new Callable<void>() 

            @Override
            public Void call() throws Exception 

                Sprinkler.getInstance().release(CONTEXT, new RuntimeException(EXCEPTION_MESSAGE));

                return null;
            

        );

        Throwable thrown = null;
        try 
            Sprinkler.getInstance().await(CONTEXT, 10000);
         catch (Throwable t) 
            // if the releaser thread delivers exception it will be externelized to this thread
            thrown = t;
        
        Assert.assertTrue(thrown instanceof SprinklerException);
        Assert.assertEquals(EXCEPTION_MESSAGE, thrown.getCause().getMessage());
    

【讨论】:

以上是关于如何使用线程执行单元测试? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何在@Transactional 方法中手动强制提交? [复制]

如何正确使用单元测试的 assertRaises() 和 NoneType 对象? [复制]

如何使用 Mockito 模拟由 Spring 应用程序上下文创建的对象以进行单元测试覆盖?

如何使用PowerShell多线程并使用Pester Mocks进行单元测试

线程运行一个我无法控制的无限操作。如何阻止它? [复制]

如何在 Jest 单元测试中模拟在 `created` Vue 生命周期挂钩中调用的方法,而不使用`shallowMount` 中已弃用的`methods` 参数? [复制]