eclipse 单元测试困境:在不运行 JBoss 或 Spring 的情况下使用 JNDI 数据源

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13052944/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-19 19:15:37  来源:igfitidea点击:

Unit Testing Dilemma: Using a JNDI data source without running JBoss or Spring

javaeclipseunit-testingjunitjndi

提问by Laura

Problem Statement

问题陈述

I want to be able to run junit tests on methods that connect to a database.

我希望能够对连接到数据库的方法运行 junit 测试。

Current setup

当前设置

Eclipse Java EE IDE– Java code is using no framework. The developers (me included) want more robust testing of current legacy code BEFORE attempting to move the code into a Spring framework so that we can prove along the way that the behavior is still correct.

Eclipse Java EE IDE– Java 代码不使用框架。开发人员(包括我在内)希望在尝试将代码移动到 Spring 框架之前对当前遗留代码进行更健壮的测试,以便我们可以证明行为仍然正确。

JBoss 4.2– Version limitation by vendor software (Adobe LiveCycle ES2); Our Java web application uses this setup of JBoss to run and makes use of the Adobe LiveCycle API.

JBoss 4.2– 供应商软件的版本限制(Adobe LiveCycle ES2);我们的 Java Web 应用程序使用 JBoss 的这种设置来运行并利用 Adob​​e LiveCycle API。

We have been unable to successfully run the vendor configured JBoss within Eclipse– we have spent weeks attempting this, including contacting the company that provides our support for the configuration of JBoss for Adobe LiveCycle. Supposedly the problem is a memory limitation issue with settings in Eclipse, but changing the memory settings has thus far failed in a successful JBoss server start within Eclipse. For now, attempting to get JBoss to run inside of Eclipse is on hold.

我们无法在 Eclipse 中成功运行供应商配置的 JBoss- 我们花了数周时间尝试这样做,包括联系为我们为 Adob​​e LiveCycle 配置 JBoss 提供支持的公司。据说问题是 Eclipse 中设置的内存限制问题,但到目前为止,更改内存设置在 Eclipse 中成功启动 JBoss 服务器失败。目前,试图让 JBoss 在 Eclipse 中运行的尝试被搁置。

The database connection is defined in a JNDI data source that JBoss loads on start up. Both our web application and Adobe LiveCycle need to create connections to this data source.

数据库连接在 JBoss 启动时加载的 JNDI 数据源中定义。我们的 Web 应用程序和 Adob​​e LiveCycle 都需要创建与此数据源的连接。

Code

代码

I am glossing over error checking and class structure in this code snippet to focus on the heart of the matter. Hopefully that does not cause problems for others. Text in square brackets is not actual text.

我在这段代码片段中掩盖了错误检查和类结构,以专注于问题的核心。希望这不会给其他人带来问题。方括号中的文本不是实际文本。

Our code to create the connection is something like this:

我们创建连接的代码是这样的:

Properties props = new Properties();
FileInputStream in = null;
in = new FileInputStream(System.getProperty("[Properties File Alias]"));
props.load(in);
String dsName = props.getProperty(“[JNDI data source name here]”); 
InitialContext jndiCntx = new InitialContext();
DataSource ds = (DataSource) jndiCntx.lookup(dsName);
Ds.getConnection();

I want to be able to test methods dependent upon this code without making any changes to it.

我希望能够测试依赖于此代码的方法而不对其进行任何更改。

Reference to properties file alias in properties-service.xml file:

引用 properties-service.xml 文件中的属性文件别名:

  <!-- ==================================================================== -->
  <!-- System Properties Service                                            -->
  <!-- ==================================================================== -->

  <!-- Allows rich access to system properties.-->

<mbean code="org.jboss.varia.property.SystemPropertiesService" 
 name="jboss:type=Service,name=SystemProperties">
  <attribute name="Properties">
    [Folder Alias]=[filepath1]
    [Properties File Alias]=[filepath2]
  </attribute>
</mbean>

Snippet from properties file located at filepath2

来自位于 filepath2 的属性文件的片段

[JNDI data source name]=java:/[JNDI data source name]

The JNDI xml file for this data source is set up like this:

此数据源的 JNDI xml 文件设置如下:

<datasources>
  <local-tx-datasource>
    <jndi-name>[JNDI data source name here]</jndi-name>
    <connection-url>jdbc:teradata://[url]/database=[database name]</connection-url>
    <driver-class>com.teradata.jdbc.TeraDriver</driver-class>
    <user-name>[user name]</user-name>
    <password>[password]</password>
    <!-- sql to call on an existing pooled connection when it is obtained from pool -->
    <check-valid-connection-sql>SELECT 1+1</check-valid-connection-sql>
  </local-tx-datasource>
</datasources>

My thoughts of where the solution may be

我对解决方案的想法

Is there something I can do in a @BeforeClass method in order to make the properties the above code is looking for available without JBoss? Maybe somehow using the setProperty method of the java.util.Properties class? I would also like to use the same JNDI xml file that JBoss reads from, if possible, in order to reduce duplicate configuration settings.

我可以在@BeforeClass 方法中做些什么,以便在没有 JBoss 的情况下使上述代码正在寻找的属性可用?也许以某种方式使用 java.util.Properties 类的 setProperty 方法?如果可能,我还想使用 JBoss 从中读取的同一个 JNDI xml 文件,以减少重复的配置设置。

So far all of my research ends with the advice “Use Spring”, but I don't think we're ready to open that can of worms yet. I am not an expert in JBoss, but if more details of our JBoss setup are needed for a helpful answer, I will do my best to get them, though I will likely need some pointers on where to look.

到目前为止,我所有的研究都以“使用 Spring”的建议结束,但我认为我们还没有准备好打开那罐蠕虫。我不是 JBoss 方面的专家,但如果需要有关 JBoss 设置的更多详细信息以获得有用的答案,我会尽我所能获得它们,尽管我可能需要一些关于在哪里查看的指示。

Stackoverflow Research references:
Jndi lookup in junit using spring
Out of container JNDI data source
Other research references:
http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Properties.html
http://docs.oracle.com/javase/jndi/tutorial/basics/prepare/initial.html

计算器研究的参考资料:
JNDI查找在JUnit利用弹簧
的容器JNDI数据源出
其他研究的参考资料:
http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Properties.html
HTTP:/ /docs.oracle.com/javase/jndi/tutorial/basics/prepare/initial.html

采纳答案by weltraumpirat

There's a very simple answer to your problem, but you're not going to like it: Don't.

您的问题有一个非常简单的答案,但您不会喜欢它:不要。

By definition, a unit test should verify the functionality of a single unit (the size of which may vary, but it should be self-sufficient). Creating a setup where the test depends upon web services, databases, etc. is counter-productive: It slows down your tests, it includes a gzillion of possible things that could go wrong (failed network connections, changes to data sets, ...) during the test, which have nothing to do with the actual code you are working on, and most importantly: It makes testing much, much harder and more complicated.

根据定义,单元测试应该验证单个单元的功能(其大小可能会有所不同,但应该是自给自足的)。在测试依赖于 Web 服务、数据库等的情况下创建一个设置会适得其反:它会减慢您的测试速度,它包括无数可能出错的事情(网络连接失败、数据集更改…… ) 在测试期间,这与您正在处理的实际代码无关,最重要的是:它使测试变得更加困难和复杂。

Instead, you should be looking for ways to decouplethe legacy code from any data sources, so that you can easily substitute mock objectsor similar test doubleswhile you are testing.

相反,您应该寻找遗留代码与任何数据源分离的方法,以便您可以在测试时轻松替换模拟对象或类似的测试替身

You shouldcreate tests to verify the integrity of your entire stack, but those are called integration tests, and they operate at a higher level of abstraction. I personally like to defer writing those until the units themselves are in place, tested and working - at least until you have come to a point where you no longer expect changes to service calls and protocols on a daily basis.

应该创建测试来验证整个堆栈的完整性,但这些称为集成测试,它们在更高的抽象级别上运行。我个人喜欢推迟编写这些内容,直到单元本身就位、测试和工作 - 至少直到您不再期望每天更改服务调用和协议的程度。

In your case, the most obvious strategy would be to encapsulate all calls to the web service in one or more separate classes, extract an interface that the business objects can depend on, and use mocks implementing that same interface for unit testing.

在您的情况下,最明显的策略是将所有对 Web 服务的调用封装在一个或多个单独的类中,提取业务对象可以依赖的接口,并使用实现相同接口的模拟进行单元测试。

For example, if you have a business object that calls an address database, you should copy the JNDI lookup code into a new service class called AddressServiceImpl. Its public methods should mimic all the method signatures of your JNDI datasource. Those, then, you extract to the AddressServiceinterface.

例如,如果您有一个调用地址数据库的业务对象,您应该将 JNDI 查找代码复制到一个名为 的新服务类中AddressServiceImpl。它的公共方法应该模仿 JNDI 数据源的所有方法签名。然后,您提取到AddressService界面。

You can then write a simple integration test to verify that the new class works: Call all the methods once and see if you get proper results. The beauty of this is that you can supply a JNDI configuration that points to a test database (instead of the original one), which you can populate with test datasets to make sure you always get the the expected results. You don't necessarily need a JBoss instance for this (though I have never had any problems with the eclipse integration) - any other JNDI provider should work, as long as the data source itself behaves the same way. And to be clear: You test this once, then forget about it. At least until the actual service methods ever change.

然后您可以编写一个简单的集成测试来验证新类是否有效:调用所有方法一次,看看是否得到正确的结果。这样做的好处在于,您可以提供指向测试数据库(而不是原始数据库)的 JNDI 配置,您可以使用测试数据集填充该配置以确保始终获得预期结果。为此,您不一定需要 JBoss 实例(尽管我在 Eclipse 集成方面从未遇到任何问题)- 只要数据源本身的行为方式相同,任何其他 JNDI 提供程序都应该可以工作。并且要明确:您测试一次,然后忘记它。至少直到实际的服务方法发生变化。

Once you verified that the service is functional, the next task is to go through all the dependent classes and replace the direct calls to the datasource with calls to the AddressService interface. And from that point on, you have a proper setup to implement unit tests on the actual business methods, without ever having to worry about things that should be tested elsewhere ;)

一旦您确认该服务正常运行,下一个任务就是检查所有依赖类并将对数据源的直接调用替换为对 AddressService 接口的调用。从那时起,您就有了一个适当的设置来对实际业务方法实施单元测试,而不必担心应该在其他地方测试的事情;)

EDIT

编辑

I second the recommendation for Mockito. Really good!

我支持Mockito的推荐。真的很好!

回答by Droj

I had a very similar situation with some legacy code in JBoss AS7, for which refactoring would have been wayout of scope.

我曾与在JBoss中AS7一些遗留代码,其中重构将是一个非常类似的情况的方式超出了范围。

I gave up on trying to get the datasource out of JBoss, because it does not support remote access to datasources, which I confirmed in trying.

我放弃了从 JBoss 中获取数据源的尝试,因为它不支持远程访问数据源,我在尝试中确认了这一点。

Ideally though, you don't want to have your unit tests dependant on a running JBoss instance in order to run, and you really don't want them to have to run insideof JBoss. It would be counter to the concept of self-contained unit tests (even though you'll still need the database to be running :) ).

理想情况下,您不希望单元测试依赖于正在运行的 JBoss 实例才能运行,而且您真的不希望它们必须JBoss内部运行。这与自包含单元测试的概念背道而驰(即使您仍然需要运行数据库:))。

Fortunately, the initial context used by your app doesn't haveto come from a running JBoss instance. After looking at this articlereferred to by an answer to another question, I was able to create my own initial context, populate it with my own datasource object.

幸运的是,你的应用程序使用的初始上下文不具有来自一个运行JBoss实例。在查看了另一个问题的答案所引用的这篇文章后,我能够创建自己的初始上下文,并使用我自己的数据源对象填充它。

This works without creating dependencies in the code because the classes under test typically run inside the container, where they simply do something like this to get the container-provided context:

这无需在代码中创建依赖项即可工作,因为被测类通常在容器内运行,它们只需执行以下操作即可获取容器提供的上下文:

InitialContext ic = new InitialContext();
DataSource ds = (DataSource)ic.lookup(DATA_SOURCE_NAME);

They don't need to specify any environment to the constructor, because it has already been set up by the container.

他们不需要为构造函数指定任何环境,因为它已经由容器设置。

In order for your unit tests to stand in for the container and provide a context, you create it, and bind a name:

为了让您的单元测试代表容器并提供上下文,您可以创建它并绑定一个名称:

InitialContext ic = new InitialContext();

// Construct DataSource
OracleConnectionPoolDataSource ds = new OracleConnectionPoolDataSource();
ds.setURL("url");
ds.setUser("username");
ds.setPassword("password");

ic.bind(DATA_SOURCE_NAME, ds);

This needs to happen in each test class's @BeforeClassmethod.

这需要在每个测试类的@BeforeClass方法中发生。

Now the classes being tested get myinitial context when running in unit tests, and the container'swhen deployed.

现在,在单元测试中运行时,正在测试的类获得我的初始上下文,并在部署时获得容器

回答by Droj

If you are using tools like Git and Maven this can be done easily with them. Check in a UnitTest specific properties file along side development and qa. Use Maven and its profilefacilities to specify a profile that copies your UnitTest file over to where it should go, same with your dev and qa when run with different profiles active.

如果您使用的是 Git 和 Maven 之类的工具,则可以通过它们轻松完成。与开发和质量检查一起检入 UnitTest 特定的属性文件。使用 Maven 及其profile工具指定一个配置文件,该配置文件将您的 UnitTest 文件复制到它应该去的地方,与您的 dev 和 qa 在使用不同配置文件运行时相同。

There is no magic to this; Spring introduces complexity more than anything. it definitly doesn't introduce simplicity like this.

这没有什么神奇之处。Spring 引入的复杂性比什么都重要。它绝对不会引入这样的简单性。

回答by Rogério

You can run your tests with a fake InitialContextimplementation, which returns whatever you need from calls to lookup(String).

您可以使用虚假InitialContext实现运行您的测试,该实现从对lookup(String).

A mocking/faking tool which allows such fake implementations is JMockit. The fake implementation would be written like the following:

允许这种虚假实现的模拟/伪造工具是JMockit。假实现将如下编写:

public class FakeInitialContext extends MockUp<InitialContext>
{
    @Mock
    public Object lookup(String name)
    {
        // Return whatever is needed based on "name".
    }
}

To apply it to a JUnit/TestNG test run, add jmockit.jar to the runtime classpath (before junit.jar if this is the case) and set the "jmockit-mocks" system property to the name of the fake class: -Djmockit-mocks=com.whatever.FakeInitialContext.

要将其应用于 JUnit/TestNG 测试运行,请将 jmockit.jar 添加到运行时类路径(如果是这种情况,在 junit.jar 之前)并将“jmockit-mocks”系统属性设置为假类的名称:-Djmockit-mocks=com.whatever.FakeInitialContext

Of course, you can also write true JUnit/TestNG unit tests where any dependency can be easily mocked, by using the "Expectations & Verifications" mocking API.

当然,您也可以编写真正的 JUnit/TestNG 单元测试,通过使用“期望和验证”模拟 API,可以轻松模拟任何依赖项。

(PS: For full disclosure, I am the creator of the JMockit project.)

(PS:为了完全披露,我是 JMockit 项目的创建者。)