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

发布于 2024-07-18 22:03:16 字数 1715 浏览 12 评论 0原文

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

让我退后一步,提出我最初提出问题的动机。

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

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.

The legacy code gets the data source this way:

(jndiName is a defined string)

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.

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?

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

海夕 2024-07-25 22:03:16

整个概念是从后到前的。 较低级别的代码不应该进行此类测试。 如果您需要不同的实现,请在相关点传递它。

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.

深巷少女 2024-07-25 22:03:16

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

作为示例,我将一个参数传递给 Ant 脚本,该脚本根据 Ear 或 War 是否进入开发、测试或生产环境来设置配置文件。

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

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.

负佳期 2024-07-25 22:03:16

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

当我使用 Spring 时,我可以从 Tomcat 移动到 WebLogic 并返回,而无需进行任何更改。 我确信,通过正确的配置,我也可以对 JBOSS 执行相同的操作。 这就是我的目标。

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.

旧故 2024-07-25 22:03:16

也许类似这样的东西(丑陋但它可能有效)

 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
  }

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 )

Then you use it like:

  if( isRunningOn("JBoss")) {
         createJBossStrategy....etcetc
  }
蓝海似她心 2024-07-25 22:03:16
Context ctx = new InitialContext(); 
  数据源 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
}

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

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

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

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.

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.

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

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.

ヅ她的身影、若隐若现 2024-07-25 22:03:16

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

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

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

如需在单元测试下移动旧代码的方向获得进一步帮助,我建议您查看 Michael Feather 的 有效地处理遗留代码

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.

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.

茶底世界 2024-07-25 22:03:16

一种简洁的方法是在 web.xml 中配置生命周期侦听器。 如果需要,它们可以设置全局标志。 例如,您可以定义 ServletContextListenerweb.xmlcontextInitialized 方法中,设置您正在容器内运行的全局标志。 如果未设置全局标志,则您不会在容器内运行。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文