我的课这样做:
public void doThing() {
Doer doer = new Doer();
Thread thread = new Thread(doer);
thread.start();
}
' Doer' class是一个内部类:
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();
并且都没有达到预期效果:它们都在我的意图被发送之前返回。
目前我已经通过在我的测试中进行了一次睡眠来解决这个问题:
Thread.sleep(10);
它可以解决这个问题,但它显然很可怕 - 它是一种等待让我感到悲伤的竞争条件。
答案 0 :(得分:2)
之前我遇到过这个问题,并且我使用了不同的方法来修复它。 我创建了一个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。
答案 1 :(得分:1)
我使用静态volatile布尔值解决了这个问题,用于通过循环锁定线程。但是对于我的线程实现,还使用回调来表示完成点。
所以在你的情况下,我会在你的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();
}
}
}
此时,您可以在任何您认为必要的地方添加断言,它们可以来自回调方法,也可以在暂停线程后测试线程计算的结果。
这并不像我希望的那样优雅,但它确实有效,并且允许我为使用线程而不是异步任务的代码部分编写单元测试。
答案 2 :(得分:0)
我会做下一步:
如果您需要进一步的行动,请告诉我
答案 3 :(得分: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]);
}
答案 4 :(得分: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()));
}