6

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

让我退后一步,问这个问题,激发我最初的问题。

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

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

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

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

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

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


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

我有一些 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。

4

7 回答 7

5

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

于 2009-05-06T21:25:10.387 回答
4

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

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

于 2009-05-06T21:56:48.970 回答
2

整个方法对我来说都是错误的。如果您的应用程序需要知道它在哪个容器中运行,那么您做错了。

当我使用 Spring 时,我可以在不更改任何内容的情况下从 Tomcat 移动到 WebLogic 并返回。我敢肯定,通过适当的配置,我也可以对 JBOSS 做同样的事情。这就是我要争取的目标。

于 2009-05-06T21:41:03.080 回答
1

也许是这样的(丑陋但它可能有效)

 private void isRunningOn( String thatServerName ) { 

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

getSpecialClassNameFor方法将返回一个对每个应用程序服务器都是唯一的类(并且在添加更多应用程序服务器时可能会返回新的类名

然后你像这样使用它:

  if( isRunningOn("JBoss")) {
         createJBossStrategy....etcetc
  }
于 2009-05-06T21:18:56.830 回答
1

有几种方法可以解决这个问题。一种是在单元测试时将 Context 对象传递给类。如果您无法更改方法签名,请将初始上下文的创建重构为受保护的方法,并通过覆盖该方法来测试返回模拟上下文对象的子类。这至少可以对类进行测试,以便您可以从那里重构为更好的替代方案。

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

需要考虑的一件事是——一旦你从容器中获得了这个数据库连接,你将如何处理它?它更容易,但如果您必须携带整个数据访问层,它就不是一个单元测试。

为了进一步帮助在单元测试下移动遗留代码的方向,我建议您查看 Michael Feather 的Working Effectively with Legacy Code

于 2009-05-06T21:21:58.383 回答
1
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 查找移动到构造函数)是否会破坏某些内容。然后当代码变得更好,更可测试时,您可以编写单元测试。(根据定义,如果测试涉及文件系统、网络或数据库,则它不是单元测试——它是集成测试。)

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

集成测试的好处是它们确保所有部分都正确插入在一起。这也很重要,但你不能经常运行它们,因为像接触数据库这样的事情会使它们变得非常慢。但是你仍然应该每天至少在你的持续集成服务器上运行一次。

于 2009-05-06T22:44:10.900 回答
-1

一个干净的方法是在web.xml. 如果您愿意,这些可以设置全局标志。例如,您可以在您的和方法中定义一个ServletContextListener,设置您在容器内运行的全局标志。如果未设置全局标志,则您没有在容器内运行。web.xmlcontextInitialized

于 2009-05-06T21:19:25.503 回答