在GAE之外测试Google App Engine ThreadManager

时间:2013-02-04 14:19:42

标签: java unit-testing google-app-engine junit

我编写了一个JUnit(4.10)单元测试,对com.google.appengine.api.ThreadManager进行了以下调用:

ThreadManager.currentRequestThreadFactory();

当此测试运行时,我会从NullPointerException方法中抛出currentRequestThreadFactory

Caused by: java.lang.NullPointerException
    at com.google.appengine.api.ThreadManager.currentRequestThreadFactory(ThreadManager.java:39)
    at com.myapp.server.plumbing.di.BaseModule.providesThreadFactory(BaseModule.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

当我下拉ThreadManager的来源,并查看第39行(这是NPE的来源)时,我看到:

public static ThreadFactory currentRequestThreadFactory() {
        return (ThreadFactory) ApiProxy.getCurrentEnvironment().getAttributes()
            .get(REQUEST_THREAD_FACTORY_ATTR);
}

所以看起来ApiProxy.getCurrentEnvironment()为空,当调用getAttribute()方法时,会抛出NPE。我通过在单元测试代码中添加一些新的打印语句来证实这一点:

if(ApiProxy.getCurrentEnvironment() == null)
    System.out.println("Environment is null.");

我隐约知道GAE为其所有服务提供“测试版本”,但未能(特别)找到如何使用它们并进行设置。所以我问:GAE是否提供这样的测试版本?如果是这样,我如何在此处添加ApiProxy测试版本?如果没有,那么我的选择是什么?我不认为我可以模拟任何一种方法(ThreadManager#currentRequestThreadFactoryApiProxy#getCurrentEnvironment),因为它们都是静态的。提前谢谢。

修改:我发现SDK附带了appengine-testing.jar。在这个JAR里面是一个ApiProxyLocal.class,我相信ApiProxy的一个版本,可以在JUnit测试期间使用,它可以在不抛弃NPE的情况下工作。如果是这种情况(我甚至不确定),那么问题是:如何将此注入我的ThreadManager进行此测试?

4 个答案:

答案 0 :(得分:3)

如果您按照以下行设置LocalServiceTestHelper,您将从存根中获取正确的线程。

private static final LocalServiceTestHelper helper = new LocalServiceTestHelper( new    LocalDatastoreServiceTestConfig());

@BeforeClass
public static void initialSetup() {
    helper.setUp();
}

@AfterClass
public static void finalTearDown() {
    helper.tearDown();
}

答案 1 :(得分:1)

我建议您不要直接从代码中调用ThreadManager.currentRequestThreadFactory()。相反,将ThreadFactory注入需要创建线程的类中。

一种简单的方法是使用Guice:

public static class MyService {
  private final ThreadFactory threadFactory;

  @Inject
  MyService(ThreadFactory threadFactory) {
    this.threadFactory = threadFactory;
  }

  ...
}

MyService的测试中,您可以将假ThreadFactory传递给MyService构造函数,也可以注入Executors.defaultThreadFactory()

在您的生产代码中,您将创建一个绑定:

bind(ThreadFactory.class)
    .toInstance(ThreadManager.currentRequestThreadFactory());

当然,如果你还没有准备好进入依赖注入,你可以创建自己的访问器来获取和设置ThreadFactory

答案 2 :(得分:0)

如果您按照建议实施ApiProxy.Environment http://code.google.com/appengine/docs/java/howto/unittesting.html

然后getAttributes()方法就是你找不到的方法 在地图中输入REQUEST_THREAD_FACTORY_ATTR

您可以添加:

attributes.put(REQUEST_THREAD_FACTORY_ATTR, new RequestThreadFactory());

有关其他有用的补充,请参阅:     http://googleappengine.googlecode.com/svn-history/trunk/java/src/main/com/google/appengine/tools/development/LocalEnvironment.java

答案 3 :(得分:0)

要添加到最后一个答案,如果要在单元测试用例中启动一个线程,那么该java线程也需要设置ApiProxy环境。

在您的类LocalServiceTestCase扩展TestCase,安装方法可能看起来 像这样的东西:

super.setUp();

helper1.setUp();

setEnvironment();

其中:

public static void setEnvironment() {

    if (ApiProxy.getCurrentEnvironment() == null) {  

        ApiProxyLocal apl = LocalServiceTestHelper.getApiProxyLocal();

        ApiProxy.setEnvironmentForCurrentThread(new TEnvironment());

        ApiProxy.setDelegate(apl);

    }
}

上述网址中给出了环境。

您可能也希望创建一个静态unsetEnvironment()。

在你的单元测试用例中,如果你已经开始一个新的java线程,你可以 在run方法的开头使用静态方法:

public void run() {

    LocalServiceTestCase.setEnvironment();