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

2022-09-02 14:01:55

问题陈述

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

当前设置

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

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

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

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

法典

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

我们创建连接的代码如下所示:

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();

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

对属性服务.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>

位于 filepath2 的属性文件中的代码段

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

此数据源的 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>

我对解决方案的想法

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

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

Stackoverflow Research References:
Jndi lookup in junit using spring
Out of container JNDI data source
其他研究参考文献:
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


答案 1

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

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

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

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

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

例如,如果您有一个调用地址数据库的业务对象,则应将 JNDI 查找代码复制到名为 的新服务类中。它的公共方法应模拟 JNDI 数据源的所有方法签名。然后,这些,你提取到接口。AddressServiceImplAddressService

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

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

编辑

我支持Mockito的建议。真的很好!


答案 2

我在 JBoss AS7 中的一些遗留代码中遇到了非常相似的情况,对于这些代码,重构将远远超出范围。

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

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

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

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

InitialContext ic = new InitialContext();
DataSource ds = (DataSource)ic.lookup(DATA_SOURCE_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);

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

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


推荐