在GAE之外测试Google App Engine ThreadManager

7

我写了一个 JUnit(4.10)单元测试,其中调用了com.google.appengine.api.ThreadManager

ThreadManager.currentRequestThreadFactory();

当此测试运行时,我会从currentRequestThreadFactory方法中抛出一个NullPointerException
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,我相信它是一个可以在JUnit测试期间使用的ApiProxy版本,可以避免抛出NPE。如果是这样(我甚至不确定),那么问题是:我该如何将其注入到此测试的ThreadManager中?

你读过 https://developers.google.com/appengine/docs/java/tools/localunittesting#Introducing_the_Java_Testing_Utilities 吗? - NamshubWriter
再次感谢 - 这些测试 JAR(在那篇文章中提到)让我可以访问本地的服务类和方法,但没有 ThreadManager 的测试版本... - IAmYourFaja
4个回答

4

如果按照以下步骤设置LocalServiceTestHelper,您将从存根中获取正确的Threads。

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

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

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

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

谢谢@NamshubWriter,但我不是在问*如何注入ThreadManager*。我是说**我无法在JUnit测试中执行com.google.appengine.api.ThreadManager.currentRequestThreadFactory**。这是因为有一个内部的、无法注入的静态方法ApiProxy.getCurrentEnvironment()抛出了NPE异常。如果我将我的应用程序部署到本地GAE应用服务器上,然后运行代码,它就可以正常工作,因为ApiProxy.getCurrentEnvironment()就不会抛出NPE异常。所以我需要找出如何在GAE之外运行此代码以进行测试。 - IAmYourFaja
@DirtyMikeAndTheBoys,我知道你想做什么。我只是建议用完全不同的方式去实现它。你的代码依赖于一个静态方法,而这个方法在生产环境和测试环境中的行为不同。这使得你的代码难以测试。我会通过依赖于一个你可以控制的类来解决这个问题。 - NamshubWriter

0
补充上一个答案,如果您在单元测试用例中启动线程,则该Java线程也需要设置ApiProxy环境。
在您的类LocalServiceTestCase扩展TestCase中,setup方法可能如下所示:
super.setUp();

helper1.setUp();

setEnvironment();

其中:

public static void setEnvironment() {

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

        ApiProxyLocal apl = LocalServiceTestHelper.getApiProxyLocal();

        ApiProxy.setEnvironmentForCurrentThread(new TEnvironment());

        ApiProxy.setDelegate(apl);

    }
}

以上网址中提供了TEnvironment。

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

在您的单元测试用例中,如果您已经启动了一个新的Java线程,您只需在run方法的开头使用静态方法即可:

public void run() {

    LocalServiceTestCase.setEnvironment();

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接