线程中的junit断言抛出异常

发布于 2024-08-28 18:37:16 字数 1202 浏览 7 评论 0原文

我做错了什么,抛出异常而不是显示失败,或者我不应该在线程内有断言吗?

 @Test
 public void testComplex() throws InterruptedException {
  int loops = 10;
  for (int i = 0; i < loops; i++) {
   final int j = i;
   new Thread() {
    @Override
    public void run() {
     ApiProxy.setEnvironmentForCurrentThread(env);//ignore this
     new CounterFactory().getCounter("test").increment();//ignore this too
     int count2 = new CounterFactory().getCounter("test").getCount();//ignore
     assertEquals(j, count2);//here be exceptions thrown. this is line 75
    }
   }.start();
  }
  Thread.sleep(5 * 1000);
  assertEquals(loops, new CounterFactory().getCounter("test").getCount());
}

堆栈跟踪

Exception in thread "Thread-26" junit.framework.AssertionFailedError: expected:<5> but was:<6>
    at junit.framework.Assert.fail(Assert.java:47)
    at junit.framework.Assert.failNotEquals(Assert.java:277)
    at junit.framework.Assert.assertEquals(Assert.java:64)
    at junit.framework.Assert.assertEquals(Assert.java:195)
    at junit.framework.Assert.assertEquals(Assert.java:201)
    at com.bitdual.server.dao.ShardedCounterTest$3.run(ShardedCounterTest.java:77)

What am I doing wrong that an exception is thrown instead of showing a failure, or should I not have assertions inside threads?

 @Test
 public void testComplex() throws InterruptedException {
  int loops = 10;
  for (int i = 0; i < loops; i++) {
   final int j = i;
   new Thread() {
    @Override
    public void run() {
     ApiProxy.setEnvironmentForCurrentThread(env);//ignore this
     new CounterFactory().getCounter("test").increment();//ignore this too
     int count2 = new CounterFactory().getCounter("test").getCount();//ignore
     assertEquals(j, count2);//here be exceptions thrown. this is line 75
    }
   }.start();
  }
  Thread.sleep(5 * 1000);
  assertEquals(loops, new CounterFactory().getCounter("test").getCount());
}

StackTrace

Exception in thread "Thread-26" junit.framework.AssertionFailedError: expected:<5> but was:<6>
    at junit.framework.Assert.fail(Assert.java:47)
    at junit.framework.Assert.failNotEquals(Assert.java:277)
    at junit.framework.Assert.assertEquals(Assert.java:64)
    at junit.framework.Assert.assertEquals(Assert.java:195)
    at junit.framework.Assert.assertEquals(Assert.java:201)
    at com.bitdual.server.dao.ShardedCounterTest$3.run(ShardedCounterTest.java:77)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

冷夜 2024-09-04 18:37:16

JUnit 框架仅捕获运行测试的主线程中的断言错误。它不知道新生成线程内的异常。
为了正确执行此操作,您应该将线程的终止状态传达给主线程。您应该正确同步线程,并使用某种共享变量来指示嵌套线程的结果。

编辑:

这是一个可以提供帮助的通用解决方案:

class AsynchTester{
    private Thread thread;
    private AssertionError exc; 

    public AsynchTester(final Runnable runnable){
        thread = new Thread(() ->
            {
                try{            
                    runnable.run();
                }catch(AssertionError e) {
                    exc = e;
                }
            }
        );
    }
    
    public void start(){
        thread.start();
    }
    
    public void test() throws InterruptedException {
        thread.join();
        if (exc != null)
            throw exc;
    }
}

您应该在构造函数中将可运行对象传递给它,然后只需调用 start() 来激活,并调用 test() 来验证。如果需要,测试方法将等待,并在主线程的上下文中抛出断言错误。

The JUnit framework captures only assertion errors in the main thread running the test. It is not aware of exceptions from within new spawn threads.
In order to do it right, you should communicate the thread's termination state to the main thread. You should synchronize the threads correctly, and use some kind of shared variable to indicate the nested thread's outcome.

EDIT:

Here is a generic solution that can help:

class AsynchTester{
    private Thread thread;
    private AssertionError exc; 

    public AsynchTester(final Runnable runnable){
        thread = new Thread(() ->
            {
                try{            
                    runnable.run();
                }catch(AssertionError e) {
                    exc = e;
                }
            }
        );
    }
    
    public void start(){
        thread.start();
    }
    
    public void test() throws InterruptedException {
        thread.join();
        if (exc != null)
            throw exc;
    }
}

You should pass it the runnable in the constructor, and then you simply call start() to activate, and test() to validate. The test method will wait if necessary, and will throw the assertion error in the main thread's context.

蒗幽 2024-09-04 18:37:16

Eyal Schneider 的答案的一个小改进:
ExecutorService 允许提交一个Callable,任何抛出的异常或错误都会被返回的Future重新抛出。
因此,测试可以写成:

@Test
public void test() throws Exception {
  ExecutorService es = Executors.newSingleThreadExecutor();
  Future<?> future = es.submit(() -> {
    testSomethingThatMightThrowAssertionErrors();
    return null;
  });

  future.get(); // This will rethrow Exceptions and Errors as ExecutionException
}

A small improvement to Eyal Schneider's answer:
The ExecutorService allows to submit a Callable and any thrown Exceptions or Errors are rethrown by the returned Future.
Consequently, the test can be written as:

@Test
public void test() throws Exception {
  ExecutorService es = Executors.newSingleThreadExecutor();
  Future<?> future = es.submit(() -> {
    testSomethingThatMightThrowAssertionErrors();
    return null;
  });

  future.get(); // This will rethrow Exceptions and Errors as ExecutionException
}
古镇旧梦 2024-09-04 18:37:16

当涉及多个工作线程时,例如在最初的问题中,仅加入其中一个是不够的。理想情况下,您需要等待所有工作线程完成,同时仍将断言失败报告回主线程,例如 Eyal 的回答。

以下是如何使用我的 ConcurrentUnit 执行此操作的简单示例:

public class MyTest extends ConcurrentTestCase {
    @Test
    public void testComplex() throws Throwable {
        int loops = 10;
        for (int i = 0; i < loops; i++) {
            new Thread(new Runnable() {
                public void run() {
                    threadAssertEquals(1, 1);
                    resume();
                }
            }).start();
        }

        threadWait(100, loops); // Wait for 10 resume calls
    }
}

Where multiple worker threads are concerned, such as in the original question, simply joining one of them is not sufficient. Ideally, you'll want to wait for all worker threads to complete while still reporting assertion failures back to the main thread, such as in Eyal's answer.

Here's a simple example of how to do this using my ConcurrentUnit:

public class MyTest extends ConcurrentTestCase {
    @Test
    public void testComplex() throws Throwable {
        int loops = 10;
        for (int i = 0; i < loops; i++) {
            new Thread(new Runnable() {
                public void run() {
                    threadAssertEquals(1, 1);
                    resume();
                }
            }).start();
        }

        threadWait(100, loops); // Wait for 10 resume calls
    }
}
作业与我同在 2024-09-04 18:37:16

JUnit 抛出继承于 Throwable 的 AssertionError,它与 Exception 具有相同的父级。您可以捕获线程的失败断言,然后保存在静态字段中,最后在主线程中检查其他线程是否使某些断言失败。

首先,创建静态字段

private volatile static Throwable excepcionTE = null;

其次,将断言包装在 try/catch 中并捕获 AssertionError

        try
    {
      assertTrue("", mensaje.contains("1234"));
    }
    catch (AssertionError e)
    {
      excepcionTE = e;
      throw e;
    }

最后,在主线程中检查该字段

 if (excepcionTE != null)
{
  excepcionTE.printStackTrace();
  fail("Se ha producido una excepcion en el servidor TE: "
      + excepcionTE.getMessage());
}

JUnit throws AssertionError that extends of Throwable, it has the same parent of Exception. You can catch the fail assert of the thread, then save in a static field and finally check in the main thread if the other thread has failed some assert.

First, create the static field

private volatile static Throwable excepcionTE = null;

Second, wrap the asserts in a try/catch and catch AssertionError

        try
    {
      assertTrue("", mensaje.contains("1234"));
    }
    catch (AssertionError e)
    {
      excepcionTE = e;
      throw e;
    }

And finally, check in the main thread that field

 if (excepcionTE != null)
{
  excepcionTE.printStackTrace();
  fail("Se ha producido una excepcion en el servidor TE: "
      + excepcionTE.getMessage());
}
七秒鱼° 2024-09-04 18:37:16

我最终使用了这种模式,它适用于可运行对象和线程。它很大程度上受到@Eyal Schneider 的回答的启发:

private final class ThreadUnderTestWrapper extends ThreadUnderTest {
    private Exception ex;

    @Override
    public void run() {
        try {
            super.run();
        } catch ( Exception ex ) {
            this.ex = ex;
        }
    }

    public Exception getException() throws InterruptedException {
        super.join(); // use runner.join here if you use a runnable. 
        return ex;
    }
}

I ended up using this pattern it work with both Runnables and Threads. It is largely inspired from the answer of @Eyal Schneider:

private final class ThreadUnderTestWrapper extends ThreadUnderTest {
    private Exception ex;

    @Override
    public void run() {
        try {
            super.run();
        } catch ( Exception ex ) {
            this.ex = ex;
        }
    }

    public Exception getException() throws InterruptedException {
        super.join(); // use runner.join here if you use a runnable. 
        return ex;
    }
}
ペ泪落弦音 2024-09-04 18:37:16

我一直在寻找一个简单且可读的解决方案。受到 Eyal Schneider 和 Riki Gomez 答案的启发,我得出了这样的结论:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;


public class ThreadExceptionTest {
    private static Throwable failedThreadException;

    @BeforeEach
    public void setup() {
        failedThreadException = null;
    }

    @Test
    public void threadTest() {
        final Thread thread = new Thread(() -> codeThatMayFail(...));

        thread.start();

        // We have to join before we check for exceptions, 
        //   otherwise we might check before the Thread even finished.
        thread.join();

        if (failedThreadException != null) {
            fail("The thread failed with an exception", failedThreadException);
        }
    }

    private void codeThatMayFail(...) {
        try {
            // Code that may throw the exception
            // ...
        } catch (Exception e) {
            failedThreadException = e;
        }
    }
}

因此,您可以借助 static 变量实现所需的结果。线程照常运行,您所要做的就是将您感兴趣的异常存储在 static 变量上。只是不要忘记在每次测试之前将其值重置为 null,否则您可能会在同一类的后续测试中遇到麻烦。

最后说明:如果您计划在同一个测试中运行多个线程,并且预计它们同时运行相同的代码块,我建议将 static 变量 易失性 以便变量的更新可预测地传播到其他线程:

private volatile static Throwable failedThreadException;

I was looking for a simple and readable solution. Inspired by Eyal Schneider and Riki Gomez's answer, I've come up with this:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;


public class ThreadExceptionTest {
    private static Throwable failedThreadException;

    @BeforeEach
    public void setup() {
        failedThreadException = null;
    }

    @Test
    public void threadTest() {
        final Thread thread = new Thread(() -> codeThatMayFail(...));

        thread.start();

        // We have to join before we check for exceptions, 
        //   otherwise we might check before the Thread even finished.
        thread.join();

        if (failedThreadException != null) {
            fail("The thread failed with an exception", failedThreadException);
        }
    }

    private void codeThatMayFail(...) {
        try {
            // Code that may throw the exception
            // ...
        } catch (Exception e) {
            failedThreadException = e;
        }
    }
}

So, you can achieve the desired result with the help of a static variable. The thread runs as usual, and all you have to do is store the exception you are interested in on the static variable. Just don't forget to reset its value to null before every test, or you may run into trouble on subsequent tests on the same Class.

Final note: If you are planning on running multiple Threads on the same test, and it is expected for them to run the same blocks of code simultaneously, I would advice to make the static variable volatile so that updates to the variable propagate predictably to other threads:

private volatile static Throwable failedThreadException;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文