机器人电气测试需要等待线程上的某些东西



我的类是这样做的:

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

Doer是一个内部类:

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

我需要用robolelectric来测试这个。自然,doThing()立即返回,我需要在执行

之前给线程一个运行的机会。
ShadowApplication.getInstance().getNextStartedService()

如何等待线程运行?

I have try:

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

和两者都没有达到预期的效果:它们都在我的Intent被发送之前返回。

现在我通过在测试中设置一个sleep来解决这个问题:

Thread.sleep(10);

,它能做到这一点,但它显然是可怕的-这是一个等待给我带来痛苦的竞争条件。

我以前遇到过这个问题,我使用了不同的方法来解决它。我为Runnable类创建了一个影子对象,并在影子构造函数中调用了run。这样,代码将立即执行,使其同步。

使用您的代码作为基础,最终结果应该类似于。

@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)注释您的测试

它的作用是当你创建一个新的对象Doer时,影子构造函数将在主线程中直接执行并调用run。

我用的是Robolectric 3.2

我用一个静态易失性布尔值来解决这个问题,这个布尔值用于用循环锁定线程。然而,对于我的线程实现也使用回调来表示完成点。

在你的例子中,我会在你的doer runnable中添加一个监听器。例如
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();
        }
    }
}
此时,您可以在任何您认为必要的地方添加断言,它们可以来自回调方法,也可以在线程暂停后测试线程计算的结果。

这并不像我希望的那样优雅,但它确实可以工作,并且允许我为使用线程而不是异步任务的代码部分编写单元测试。

下面是一个工作示例。

请注意,它依赖于一个调用来告诉robolelectric启用实时HTTP查询:

FakeHttp.getFakeHttpLayer().interceptHttpRequests(false);

代码使用ConditionVariable来管理对后台任务完成情况的跟踪。

我必须将其添加到我的项目构建中。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]);
}

可以使用监视器锁

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()));
}

马克,两个建议:

  1. 测试中只有一个线程(除非是特殊测试)
  2. 对象的单独实例化及其使用

我接下来要做的是:

  1. 介绍一些负责线程创建的工厂
  2. 在测试中模拟
  3. 在测试中捕获可运行的,并在同一线程上运行
  4. 验证服务是否启动

如果你需要进一步的解释请告诉我

最新更新