2015-09-22 1 views
3

Моего класс делает это:Robolectric тест должен ждать чего-то на нитке

public void doThing() { 
    Doer doer = new Doer(); 
    Thread thread = new Thread(doer); 
    thread.start(); 
} 

класса «делатель» является внутренним классом:

private class Doer implements Runnable { 
    public void run() { 
     Intent myIntent = new Intent(mContext, MyService.class); 
     mContext.startService(myIntent); 

     ...Some more stuff... 
    } 

Это прекрасно работает.

Мне нужно проверить это с помощью Robolectric. Естественно doThing() возвращается немедленно, и мне нужно, чтобы дать нить возможность работать, прежде чем я

ShadowApplication.getInstance().getNextStartedService() 

Как я могу ждать нить бежать?

Я попробовал:

Robolectric.flushForegroundThreadScheduler(); 
Robolectric.flushBackgroundThreadScheduler(); 

и ни к желаемому эффекту: они оба до моего возвращения Intent было отправлено.

В настоящее время я работал вокруг него, поставив сон в мой тест:

Thread.sleep(10); 

и это делает трюк, но это явно ужасно - это состояние гонки ждет, чтобы причинить мне горе.

+0

Почему вы используете класс Thread вместо оболочки, такой как класс AsyncTask? У Robolectric уже есть механизм, позволяющий избежать параллельного выполнения кода при использовании AsyncTask. 'Robolectric.flushBackgroundThreadScheduler()' может иметь эффект только при использовании механизма, который можно легко затенять robolectric, например AsyncTask. – nenick

+0

Поскольку мне нужно было передать нагрузку параметров (не показано в моем фрагменте), а реализация Thread была проще для того, что мне нужно было сделать ... Однако я думаю, что вы правы, преимущества выигрыша ShadowAsyncTask - и вот что я использовал. –

ответ

0

Марк, две советы:

  1. только один поток в тестах (если это не специальный тест)
  2. отдельные конкретизации объекта и его использование

Я хотел бы сделать следующий:

  1. Представьте какую-нибудь фабрику, отвечающую за создание резьбы
  2. подражает ей в тесте
  3. Capture исполняемых в тесте и запустить его на тот же поток
  4. Убедитесь, что служба была запущена

Позволь мне знать, если вам нужна дополнительная exaplnations

+0

Спасибо за ответ. Вместо того, чтобы разделить его на два класса, я перешел к AsyncTask, как предложил Неник. –

+0

До вас, но для меня вы ничего хорошего не делаете –

1

я работал вокруг эта проблема со статическим volatile boolean, используемым для блокировки потока с помощью цикла. Однако для реализации моего потока также использовались обратные вызовы для обозначения точек завершения.

Так что в вашем случае я бы добавил слушателя в ваш исполняемый файл. Пример

private class Doer implements Runnable { 
    Interface FinishedListener { 
     finished(); 
    } 

    FinishedListener mListener; 

    public Doer(FinishedListener listener) { 
     mListener = listener; 
    } 

    public void run() { 
     Intent myIntent = new Intent(mContext, MyService.class); 
     mContext.startService(myIntent); 

     ...Some more stuff... 

     mListener.finished(); 
    } 

Также добавьте возможность передавать слушателю функцию doThing. Тогда в вашем тесте сделайте что-нибудь подобное.

static volatile boolean sPauseTest; 
@Test 
public void test_doThing() { 
    sPauseTest = true; 
    doThing(new FinishedListener() { 
      @Override 
      public void finished() { 
       sPauseTest = false; 
      } 
    }); 

    while (sPauseTest) { 
     try { 
      Thread.sleep(100); 
     } catch(InterruptedException ex) { 
      Thread.currentThread().interrupt(); 
     } 
    } 
} 

На данный момент вы можете добавить утверждает, куда вы сочтете необходимыми, они могут быть как из метода обратного вызова или после паузы нитей, чтобы проверить результаты расчетов ниток.

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

0

Настоящий рабочий пример.

Обратите внимание, что она опирается на вызов сказать Robolectric для того, чтобы живые запросы HTTP:

FakeHttp.getFakeHttpLayer().interceptHttpRequests(false); 

Код ConditionVariable управлять прослеживание завершения фоновой задачи.

я должен был добавить это build.gradle файл моего проекта (в блоке зависимостей):

// http://robolectric.org/using-add-on-modules/ 
compile 'org.robolectric:shadows-httpclient:3.0' 
testCompile 'org.robolectric:shadows-httpclient:3.0' 

Я надеюсь, что это помогает!

Пит

// This is a dummy class that makes a deferred HTTP call. 
static class ItemUnderTest { 

    interface IMyCallbackHandler { 
    void completedWithResult(String result); 
    } 

    public void methodUnderTestThatMakesDeferredHttpCall(final IMyCallbackHandler completion) { 
    // Make the deferred HTTP call in here. 
    // Write the code such that completion.completedWithResult(...) is called once the 
    // Http query has completed in a separate thread. 

    // This is just a dummy/example of how things work! 
    new Thread() { 
     @Override 
     public void run() { 
     // Thread entry point. 
     // Pretend our background call was handled in some way. 
     completion.completedWithResult("Hello"); 
     } 
    }.start(); 
    } 
} 

@org.junit.Test 
public void testGetDetailedLatestResultsForWithInvalidEmailPasswordUnit_LiveQuery() throws Exception { 

    // Tell Robolectric that we want to perform a "Live" test, against the real underlying server. 
    FakeHttp.getFakeHttpLayer().interceptHttpRequests(false); 

    // Construct a ConditionVariable that is used to signal to the main test thread, 
    // once the background work has completed. 
    final ConditionVariable cv = new ConditionVariable(); 

    // Used to track that the background call really happened. 
    final boolean[] responseCalled = {false}; 

    final ItemUnderTest itemUnderTest = new ItemUnderTest(); 

    // Construct, and run, a thread to perform the background call that we want to "wait" for. 
    new Thread() { 
    @Override 
    public void run() { 
     // Thread entry point. 
     // Make the call that does something in the background...! 
     itemUnderTest.methodUnderTestThatMakesDeferredHttpCall(
      new ItemUnderTest.IMyCallbackHandler() { 
      @Override 
      public void completedWithResult(String result) { 
       // This is intended to be called at some point down the line, outside of the main thread. 
       responseCalled[0] = true; 

       // Verify the result is what you expect, in some way! 
       org.junit.Assert.assertNotNull(result); 

       // Unblock the ConditionVariable... so the main thread can complete 
       cv.open(); 
      } 
      } 
    ); 

     // Nothing to do here, in particular... 
    } 
    }.start(); 

    // Perform a timed-out wait for the background work to complete. 
    cv.block(5000); 

    org.junit.Assert.assertTrue(responseCalled[0]); 
} 
0

Вы можете использовать блокировку монитора.

private final Object monitorLock = new Object(); 
private final AtomicBoolean isServiceStarted = new AtomicBoolean(false); 

@Test 
public void startService_whenRunnableCalled(final Context context) { 
    Thread startServiceThread = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      context.startService(new Intent(context, MyService.class)); 
      isServiceStarted.set(true); 
      // startServiceThread acquires monitorLock. 
      synchronized (monitorLock) { 
       // startServiceThread moves test thread to BLOCKING 
       monitorLock.notifyAll(); 
      } 
      // startServiceThread releases monitorLock 
      // and test thread is moved to RUNNING 
     } 
    }); 
    startServiceThread.start(); 
    while (!isServiceStarted.get()) { 
     // test thread acquires monitorLock. 
     synchronized (monitorLock) { 
      // test thread is now WAITING, monitorLock released. 
      monitorLock.wait(); 
      // test thread is now BLOCKING. 

      // When startServiceThread releases monitorLock, 
      // test thread will re-acquire it and be RUNNING. 
     } 
     // test thread releases monitorLock 
    } 
    Intent intent = ShadowApplication.getInstance().getNextStartedService(); 
    assertThat(intent.getComponent().getClassName(), is(MyService.class.getName())); 
} 
2

У меня была эта проблема раньше, и я использовал другой подход, чтобы исправить это. Я создал теневой объект моего класса Runnable и вызвал прогон в теневом конструкторе. Таким образом, код будет выполняться немедленно, делая его синхронным.

Используя ваш код в качестве основы, конечный результат должен быть чем-то вроде.

@Implements(Doer.class) 
private class ShadowDoer{ 
    @RealObject 
    private Doer doer; 

    // Execute after Doer constructor 
    public void __constructor__(<Doer construtor arguments>) { 
     doer.run(); 
    } 
} 

Затем аннотировать тест с @Config(shadows=ShadowDoer.class)

Что это делает, когда вы создаете новый объект Деятель, теневая конструктор будет выполнять и вызывать запуск непосредственно в главном потоке.

Я использовал Robolectric 3.2.