谢谢大家的帮助。你发布了一些(我应该预料到的)答案,表明我的整个方法都是错误的,或者低级代码永远不必知道它是否在容器中运行。我倾向于同意。但是,我正在处理一个复杂的遗留应用程序,并且没有选择对当前问题进行重大重构。

让我退后一步,问问题是我原来问题的动机。

我有一个在JBoss下运行的遗留应用程序,并对低级代码进行了一些修改。我为我的修改创建了一个单元测试。为了运行测试,我需要连接到数据库。

遗留代码以这种方式获取数据源:

(jndiName是一个已定义的字符串)

Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

我的问题是,当我在单元测试下运行此代码时,Context没有定义数据源。我的解决方案是尝试查看我是否在应用程序服务器下运行,如果没有,则创建测试DataSource并返回它。如果我在app服务器下运行,那么我使用上面的代码。

所以,我的真正的问题是:这样做的正确方法是什么?是否有一些批准的方式单元测试可以设置上下文以返回适当的数据源,以便被测代码不需要知道它在哪里运行?


对于上下文:我的原始问题:

我有一些Java代码需要知道它是否在JBoss下运行。代码是否有规范的方式来判断它是否在容器中运行?

我的第一种方法是通过实验开发的,包括获取初始上下文和测试它可以查找某些值。

private boolean isRunningUnderJBoss(Context ctx) {
        boolean runningUnderJBoss = false;
        try {
            // The following invokes a naming exception when not running under
            // JBoss.
            ctx.getNameInNamespace();

            // The URL packages must contain the string "jboss".
            String urlPackages = (String) ctx.lookup("java.naming.factory.url.pkgs");
            if ((urlPackages != null) && (urlPackages.toUpperCase().contains("JBOSS"))) {
                runningUnderJBoss = true;
            }
        } catch (Exception e) {
            // If we get there, we are not under JBoss
            runningUnderJBoss = false;
        }
        return runningUnderJBoss;
    }

Context ctx = new InitialContext();
if (isRunningUnderJboss(ctx)
{
.........

现在,这似乎有效,但感觉就像是黑客。什么是“正确”的这样做的方法?理想情况下,我想要一种适用于各种应用服务器的方法,而不仅仅是JBoss。

有帮助吗?

解决方案

整个方法对我来说都是错误的。如果你的应用需要知道它正在运行哪个容器你做错了什么。

当我使用Spring时,我可以从Tomcat迁移到WebLogic并返回而不会改变任何内容。我确信通过适当的配置,我也可以用JBOSS做同样的技巧。这就是我要拍摄的目标。

其他提示

整个概念都回到了前面。低级代码不应该进行这种测试。如果您需要不同的实现,请在相关点传递它。

依赖注入的某种组合(无论是通过Spring,配置文件还是程序参数)和工厂模式通常都能发挥最佳效果。

作为一个例子,我将一个参数传递给我的Ant脚本,这些脚本根据耳朵或战争进入开发,测试或生产环境来设置配置文件。

也许这样的事情(丑陋但可能有用)

 private void isRunningOn( String thatServerName ) { 

     String uniqueClassName = getSpecialClassNameFor( thatServerName );
     try { 
         Class.forName( uniqueClassName );
     } catch ( ClassNotFoudException cnfe ) { 
         return false;
     }
     return true;
 } 

getSpecialClassNameFor 方法将返回每个Application Server唯一的类(并且在添加更多应用服务器时可能返回新的类名)

然后你就像使用它一样:

  if( isRunningOn("JBoss")) {
         createJBossStrategy....etcetc
  }
Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

谁构造了InitialContext?它的构造必须在你试图测试的代码之外,否则你将无法模拟上下文。

由于您说您正在处理遗留应用程序,因此首先重构代码,以便您可以轻松地将上下文或数据源依赖注入到类中。然后,您可以更轻松地为该类编写测试。

您可以通过使用两个构造函数来转换遗留代码,如下面的代码所示,直到您重构了构造类的代码。这样您就可以更轻松地测试Foo,并且可以保持使用Foo的代码不变。然后你可以慢慢地重构代码,这样就可以完全删除旧的构造函数,并且依赖注入所有依赖项。

public class Foo {
  private final DataSource dataSource;
  public Foo() { // production code calls this - no changes needed to callers
    Context ctx = new InitialContext();
    this.dataSource = (DataSource) ctx.lookup(jndiName);
  }
  public Foo(DataSource dataSource) { // test code calls this
    this.dataSource = dataSource;
  }
  // methods that use dataSource
}

但是在开始进行重构之前,你应该进行一些集成测试以覆盖你的背部。否则,您无法知道即使是简单的重构,例如将DataSource查找移动到构造函数,也会破坏某些内容。然后,当代码变得更好,更可测试时,您可以编写单元测试。 (根据定义,如果测试涉及文件系统,网络或数据库,则不是单元测试 - 它是集成测试。)

单元测试的好处是它们运行速度快 - 每秒数百或数千 - 并且非常专注于一次只测试一种行为。这使得它可以经常运行(如果你在更改一行后犹豫不决地运行所有单元测试,它们运行得太慢),这样你就可以获得快速反馈。而且因为它们非常专注,所以只需通过查看失败测试的名称即可知道错误的生产代码中的确切位置。

集成测试的好处是它们确保所有部件都正确地插在一起。这也很重要,但是你不能经常运行它们,因为触摸数据库之类的东西会使它们非常慢。但是你仍然应该每天至少在持续集成服务器上运行它们一次。

有几种方法可以解决这个问题。一种是在单元测试下将Context对象传递给类。如果无法更改方法签名,则将创建初始上下文重构为受保护方法,并通过重写方法来测试返回模拟上下文对象的子类。这至少可以使课程受到考验,因此你可以从那里重构更好的选择。

下一个选项是使数据库连接成为可以判断它是否在容器中的工厂,并在每种情况下都做适当的事情。

要考虑的一件事是 - 一旦你从容器中获得了这个数据库连接,你打算用它做什么?它更容易,但如果你必须携带整个数据访问层,它就不是一个单元测试。

为了在单元测试下移动遗留代码的方向提供进一步的帮助,我建议你看看Michael Feather的有效地使用旧版代码

执行此操作的一种简洁方法是在 web.xml 中配置生命周期侦听器。如果需要,可以设置全局标志。例如,您可以定义 ServletContextListener web.xml contextInitialized 方法中,设置一个在容器内运行的全局标志。如果未设置全局标志,则表示您未在容器内运行。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top