我的类是这样做的:
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()));
}
马克,两个建议:
- 测试中只有一个线程(除非是特殊测试)
- 对象的单独实例化及其使用
我接下来要做的是:
- 介绍一些负责线程创建的工厂
- 在测试中模拟
- 在测试中捕获可运行的,并在同一线程上运行
- 验证服务是否启动
如果你需要进一步的解释请告诉我