junit assert в потоке выдает исключение

Что я делаю неправильно, что возникает исключение, а не показывается сбой, или у меня не должно быть утверждений внутри потоков?

 @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)

person antony.trupe    schedule 07.04.2010    source источник
comment
Почему вы создаете новую тему в этом тесте? Я имею в виду, почему х@$! вы хотите создать потоки в модульном тесте?   -  person Cem Catikkas    schedule 08.04.2010
comment
@Cem У меня есть набор (начальных) тестов, которые я разрабатываю, и один из них (попытки) обнаружить состояние гонки (3 строки, которые я говорю игнорировать, становятся актуальными для этого обсуждения). Есть ли лучший способ провести тестирование состояния гонки? Нужно ли мне переходить на другой инструмент для такого рода тестов?   -  person antony.trupe    schedule 08.04.2010
comment
Вы не можете проверить условия гонки с помощью модульных тестов, особенно путем создания потоков для имитации ситуаций. Даже в приведенном вами примере вы проверяете, что счетчику лучше быть равным 2, когда работает второй поток. Даже если вы создаете потоки по порядку, они не обязательно будут выполняться в одном и том же порядке. Кроме того, потоки могут быть вытеснены между моментом вызова increment и get, поэтому в вашем тесте уже есть состояние гонки. Каждый раз в голубой луне это пройдет или потерпит неудачу. Модульные тесты должны быть более детерминированными, чем это.   -  person Cem Catikkas    schedule 08.04.2010
comment
Конечно, конкретные утверждения были немного наивными, но модульные тесты невероятно эффективны для выявления значительной части проблем с гонками/конфликтами в моей конкретной ситуации.   -  person antony.trupe    schedule 09.04.2010


Ответы (5)


Платформа JUnit фиксирует только ошибки утверждений в основном потоке, выполняющем тест. Он не знает об исключениях из новых порожденных потоков. Чтобы сделать это правильно, вы должны сообщить о состоянии завершения потока основному потоку. Вы должны правильно синхронизировать потоки и использовать некоторую общую переменную для указания результата вложенного потока.

РЕДАКТИРОВАТЬ:

Вот универсальное решение, которое может помочь:

class AsynchTester{
    private Thread thread;
    private AssertionError exc; 

    public AsynchTester(final Runnable runnable){
        thread = new Thread(new Runnable(){
            public void run(){
                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;
    }
}

Вы должны передать его runnable в конструкторе, а затем вы просто вызываете start() для активации и test() для проверки. Тестовый метод будет ждать, если это необходимо, и выдаст ошибку утверждения в контексте основного потока.

person Eyal Schneider    schedule 07.04.2010
comment
Вы должны правильно синхронизировать потоки... в этом примере простой способ состоит в том, чтобы основной поток вызывал join() в дочернем потоке... и избавлялся от вызова sleep(5000). - person Stephen C; 08.04.2010
comment
Вызов сна немного пах, но я не стал на нем останавливаться, так как это был код модульного тестирования, но теперь, когда я знаю, я наверняка буду использовать правильный способ. - person antony.trupe; 08.04.2010
comment
FWIW exc не обязательно должен быть изменчивым, так как Thread.join() синхронизирует состояние присоединяемого потока с присоединяемым потоком. - person eregon; 28.03.2018
comment
@eregon: Ты прав. Отношение «происходит до» хорошо установлено для этого члена данных. Исправлено. - person Eyal Schneider; 29.03.2018

Небольшое улучшение ответа Эяля Шнайдера:
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
}
person MyKey_    schedule 15.11.2016

Когда речь идет о нескольких рабочих потоках, например, в исходном вопросе, простого присоединения к одному из них недостаточно. В идеале вы захотите дождаться завершения всех рабочих потоков, продолжая сообщать об ошибках утверждения обратно в основной поток, например, в ответе Эяля.

Вот простой пример того, как это сделать с помощью 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
    }
}
person Jonathan    schedule 25.10.2010

В итоге я использовал этот шаблон, он работает как с Runnables, так и с потоками. Это во многом вдохновлено ответом @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;
    }
}
person Snicolas    schedule 04.12.2012

JUnit выдает AssertionError, который расширяет Throwable, он имеет того же родителя, что и 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());
}
person Riki Gomez    schedule 11.01.2019