Java 当不在应用服务器中运行时,单元测试应该如何设置数据源?

Java 当不在应用服务器中运行时,单元测试应该如何设置数据源?,java,jakarta-ee,jboss,containers,detect,Java,Jakarta Ee,Jboss,Containers,Detect,谢谢大家的帮助。你们中的许多人(正如我所预料的)给出了答案,表明我的整个方法是错误的,或者底层代码永远不必知道它是否在容器中运行。我倾向于同意。但是,我处理的是一个复杂的遗留应用程序,无法对当前问题进行重大重构 让我退一步,问我原来的问题 我有一个在JBoss下运行的遗留应用程序,并对低级代码进行了一些修改。我已经为我的修改创建了一个单元测试。为了运行测试,我需要连接到数据库 旧代码通过以下方式获取数据源: (jndiName是一个已定义的字符串) 我的问题是,当我在单元测试下运行这段代码时,上

谢谢大家的帮助。你们中的许多人(正如我所预料的)给出了答案,表明我的整个方法是错误的,或者底层代码永远不必知道它是否在容器中运行。我倾向于同意。但是,我处理的是一个复杂的遗留应用程序,无法对当前问题进行重大重构

让我退一步,问我原来的问题

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

旧代码通过以下方式获取数据源:

(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。

也许是这样的(虽然很难看,但可能会工作)

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

然后你可以像这样使用它:

  if( isRunningOn("JBoss")) {
         createJBossStrategy....etcetc
  }

一种干净的方法是在
web.xml
中配置生命周期侦听器。如果需要,可以设置全局标志。例如,您可以在
web.xml
中定义,并在
contextInitialized
方法中设置一个全局标志,表明您正在容器中运行。如果未设置全局标志,则表示您没有在容器内运行。

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

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

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


要在单元测试下移动遗留代码的这一方向上获得进一步帮助,我建议您看看Michael Feather的。

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

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


当我使用Spring时,我可以从Tomcat移动到WebLogic,然后再移动回来,而不做任何更改。我确信,通过适当的配置,我也可以对JBOSS执行同样的操作。这就是我要争取的目标。

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

例如,我将一个参数传递给我的Ant脚本,根据ear或war是进入开发、测试还是生产环境来设置配置文件

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

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

您可以通过使用两个构造函数来转换遗留代码,如下面的代码所示,直到重构了构造类的代码为止。这样,您可以更容易地测试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
}
但在开始重构之前,应该进行一些集成测试。否则,即使是简单的重构,例如将数据源查找移动到构造函数,您也无法知道是否会破坏某些东西。然后,当代码变得更好、更可测试时,您可以编写单元测试。(根据定义,如果测试涉及文件系统、网络或数据库,则它不是单元测试,而是集成测试。)

单元测试的好处是它们运行速度很快——每秒数百或数千次——并且非常专注于一次只测试一种行为。这使得经常运行(如果您在更改一行之后犹豫运行所有单元测试,那么它们运行得太慢)成为可能,以便获得快速反馈。因为他们非常专注,你会知道
  if( isRunningOn("JBoss")) {
         createJBossStrategy....etcetc
  }
Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);
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
}