单元测试在未在应用程序服务器中运行时应如何设置数据源?

时间:2022-02-08 22:46:27

Thank you all for your help. A number of you posted (as I should have expected) answers indicating my whole approach was wrong, or that low-level code should never have to know whether or not it is running in a container. I would tend to agree. However, I'm dealing with a complex legacy application and do not have the option of doing a major refactoring for the current problem.

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

Let me step back and ask the question the motivated my original question.

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

I have a legacy application running under JBoss, and have made some modifications to lower-level code. I have created a unit test for my modification. In order to run the test, I need to connect to a database.

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

The legacy code gets the data source this way:

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

(jndiName is a defined string)

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

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

My problem is that when I run this code under unit test, the Context has no data sources defined. My solution to this was to try to see if I'm running under the application server and, if not, create the test DataSource and return it. If I am running under the app server, then I use the code above.

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

So, my real question is: What is the correct way to do this? Is there some approved way the unit test can set up the context to return the appropriate data source so that the code under test doesn't need to be aware of where it's running?

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


For Context: MY ORIGINAL QUESTION:

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

I have some Java code that needs to know whether or not it is running under JBoss. Is there a canonical way for code to tell whether it is running in a container?

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

My first approach was developed through experimention and consists of getting the initial context and testing that it can look up certain values.

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

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)
{
.........

Now, this seems to work, but it feels like a hack. What is the "correct" way to do this? Ideally, I'd like a way that would work with a variety of application servers, not just JBoss.

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

7 个解决方案

#1


The whole approach feels wrong headed to me. If your app needs to know which container it's running in you're doing something wrong.

整个方法感觉错了我。如果您的应用需要知道它正在运行哪个容器,那么您做错了什么。

When I use Spring I can move from Tomcat to WebLogic and back without changing anything. I'm sure that with proper configuration I could do the same trick with JBOSS as well. That's the goal I'd shoot for.

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

#2


The whole concept is back to front. Lower level code should not be doing this sort of testing. If you need a different implementation pass it down at a relevant point.

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

#3


Some combination of Dependency Injection (whether through Spring, config files, or program arguments) and the Factory Pattern would usually work best.

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

As an example I pass an argument to my Ant scripts that setup config files depending if the ear or war is going into a development, testing, or production environment.

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

#4


Perhaps something like this ( ugly but it may work )

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

 private void isRunningOn( String thatServerName ) { 

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

The getSpecialClassNameFor method would return a class that is unique for each Application Server ( and may return new class names when more apps servers are added )

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

Then you use it like:

然后你使用它像:

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

#5


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

Who constructs the InitialContext? Its construction must be outside the code that you are trying to test, or otherwise you won't be able to mock the context.

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

Since you said that you are working on a legacy application, first refactor the code so that you can easily dependency inject the context or data source to the class. Then you can more easily write tests for that class.

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

You can transition the legacy code by having two constructors, as in the below code, until you have refactored the code that constructs the class. This way you can more easily test Foo and you can keep the code that uses Foo unchanged. Then you can slowly refactor the code, so that the old constructor is completely removed and all dependencies are dependency injected.

您可以通过使用两个构造函数来转换遗留代码,如下面的代码所示,直到您重构了构造类的代码。这样您就可以更轻松地测试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
}

But before you start doing that refactoring, you should have some integration tests to cover your back. Otherwise you can't know whether even the simple refactorings, such as moving the DataSource lookup to the constructor, break something. Then when the code gets better, more testable, you can write unit tests. (By definition, if a test touches the file system, network or database, it is not a unit test - it is an integration test.)

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

The benefit of unit tests is that they run fast - hundreds or thousands per second - and are very focused to testing just one behaviour at a time. That makes it possible run then often (if you hesitate running all unit tests after changing one line, they run too slowly) so that you get quick feedback. And because they are very focused, you will know just by looking at the name of the failing test that exactly where in the production code the bug is.

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

The benefit of integration tests is that they make sure that all parts are plugged together correctly. That is also important, but you can not run them very often because things like touching the database make them very slow. But you should still run them at least once a day on your continuous integration server.

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

#6


There are a couple of ways to tackle this problem. One is to pass a Context object to the class when it is under unit test. If you can't change the method signature, refactor the creation of the inital context to a protected method and test a subclass that returns the mocked context object by overriding the method. That can at least put the class under test so you can refactor to better alternatives from there.

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

The next option is to make database connections a factory that can tell if it is in a container or not, and do the appropriate thing in each case.

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

One thing to think about is - once you have this database connection out of the container, what are you going to do with it? It is easier, but it isn't quite a unit test if you have to carry the whole data access layer.

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

For further help in this direction of moving legacy code under unit test, I suggest you look at Michael Feather's Working Effectively with Legacy Code.

为了在单元测试下移动遗留代码的这个方向提供进一步的帮助,我建议你看看Michael Feather的遗留代码的有效工作。

#7


A clean way to do this would be to have lifecycle listeners configured in web.xml. These can set global flags if you want. For example, you could define a ServletContextListener in your web.xml and in the contextInitialized method, set a global flag that you're running inside a container. If the global flag is not set, then you are not running inside a container.

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

#1


The whole approach feels wrong headed to me. If your app needs to know which container it's running in you're doing something wrong.

整个方法感觉错了我。如果您的应用需要知道它正在运行哪个容器,那么您做错了什么。

When I use Spring I can move from Tomcat to WebLogic and back without changing anything. I'm sure that with proper configuration I could do the same trick with JBOSS as well. That's the goal I'd shoot for.

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

#2


The whole concept is back to front. Lower level code should not be doing this sort of testing. If you need a different implementation pass it down at a relevant point.

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

#3


Some combination of Dependency Injection (whether through Spring, config files, or program arguments) and the Factory Pattern would usually work best.

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

As an example I pass an argument to my Ant scripts that setup config files depending if the ear or war is going into a development, testing, or production environment.

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

#4


Perhaps something like this ( ugly but it may work )

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

 private void isRunningOn( String thatServerName ) { 

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

The getSpecialClassNameFor method would return a class that is unique for each Application Server ( and may return new class names when more apps servers are added )

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

Then you use it like:

然后你使用它像:

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

#5


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

Who constructs the InitialContext? Its construction must be outside the code that you are trying to test, or otherwise you won't be able to mock the context.

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

Since you said that you are working on a legacy application, first refactor the code so that you can easily dependency inject the context or data source to the class. Then you can more easily write tests for that class.

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

You can transition the legacy code by having two constructors, as in the below code, until you have refactored the code that constructs the class. This way you can more easily test Foo and you can keep the code that uses Foo unchanged. Then you can slowly refactor the code, so that the old constructor is completely removed and all dependencies are dependency injected.

您可以通过使用两个构造函数来转换遗留代码,如下面的代码所示,直到您重构了构造类的代码。这样您就可以更轻松地测试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
}

But before you start doing that refactoring, you should have some integration tests to cover your back. Otherwise you can't know whether even the simple refactorings, such as moving the DataSource lookup to the constructor, break something. Then when the code gets better, more testable, you can write unit tests. (By definition, if a test touches the file system, network or database, it is not a unit test - it is an integration test.)

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

The benefit of unit tests is that they run fast - hundreds or thousands per second - and are very focused to testing just one behaviour at a time. That makes it possible run then often (if you hesitate running all unit tests after changing one line, they run too slowly) so that you get quick feedback. And because they are very focused, you will know just by looking at the name of the failing test that exactly where in the production code the bug is.

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

The benefit of integration tests is that they make sure that all parts are plugged together correctly. That is also important, but you can not run them very often because things like touching the database make them very slow. But you should still run them at least once a day on your continuous integration server.

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

#6


There are a couple of ways to tackle this problem. One is to pass a Context object to the class when it is under unit test. If you can't change the method signature, refactor the creation of the inital context to a protected method and test a subclass that returns the mocked context object by overriding the method. That can at least put the class under test so you can refactor to better alternatives from there.

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

The next option is to make database connections a factory that can tell if it is in a container or not, and do the appropriate thing in each case.

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

One thing to think about is - once you have this database connection out of the container, what are you going to do with it? It is easier, but it isn't quite a unit test if you have to carry the whole data access layer.

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

For further help in this direction of moving legacy code under unit test, I suggest you look at Michael Feather's Working Effectively with Legacy Code.

为了在单元测试下移动遗留代码的这个方向提供进一步的帮助,我建议你看看Michael Feather的遗留代码的有效工作。

#7


A clean way to do this would be to have lifecycle listeners configured in web.xml. These can set global flags if you want. For example, you could define a ServletContextListener in your web.xml and in the contextInitialized method, set a global flag that you're running inside a container. If the global flag is not set, then you are not running inside a container.

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