【问题标题】:Unit Testing Dilemma: Using a JNDI data source without running JBoss or Spring单元测试困境:在不运行 JBoss 或 Spring 的情况下使用 JNDI 数据源
【发布时间】:2012-10-14 17:07:02
【问题描述】:

问题陈述

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

当前设置

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

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

我们无法在 Eclipse 中成功运行供应商配置的 JBoss——我们花了数周时间尝试此操作,包括联系为 Adob​​e LiveCycle 配置 JBoss 提供支持的公司。假设问题是 Eclipse 中设置的内存限制问题,但迄今为止,在 Eclipse 中成功启动 JBoss 服务器时更改内存设置失败。目前,让 JBoss 在 Eclipse 中运行的尝试暂时搁置。

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

代码

我在这段代码 sn-p 中忽略了错误检查和类结构,以关注问题的核心。希望这不会给其他人带来问题。方括号中的文本不是实际文本。

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

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

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

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>

来自位于 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 文件,以减少重复的配置设置。

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

Stackoverflow 研究参考资料:
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
@ 987654324@

【问题讨论】:

  • “我希望能够在不对其进行任何更改的情况下测试依赖于此代码的方法。” -- 如果你想测试依赖于这段代码的方法中的逻辑,你可以模拟这个连接并仍然测试那些方法吗?
  • 你检查过 Mockito (code.google.com/p/mockito) 吗?这是一个很棒的模拟框架,可以成为模拟 JNDI 连接的好帮手
  • Properties.load() 采用 Reader 或 InputStream。你能澄清一下[name of properties file here]到底是什么吗?
  • 致artbristol:这是一个输入流。我将扩展代码以包含这些详细信息,因为再看一遍,那里有另一个对 JBoss 设置的属性的引用。
  • 我目前正在根据 Zack Macomber 和 weltraumpirat 的建议研究 mockito。

标签: java eclipse unit-testing junit jndi


【解决方案1】:

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

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

相反,您应该寻找将旧代码与任何数据源解耦的方法,以便在测试时轻松替换mock objects 或类似的test doubles。

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

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

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

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

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

编辑

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

【讨论】:

  • 我之所以选择此响应,是因为单元测试和集成测试的区别以及良好的战略建议之间的帮助。我仍在比较 mockito 和 JMockit(由 Rogerio 提到)。甚至 mockito 的文档也表明 JMockit 可能是某些类型的遗留代码的更好选择。我有一些分析工作摆在我面前。谢谢你,stackoverflow!
  • 非常好的答案!不过,请注意几点:避免使用 *Impl 或 Default* 之类的名称。这样的名称没有提供信息,它们通常表明存在设计缺陷(例如,您抽象了一些不应该拥有的东西)。您宁愿根据子类型的核心实现特征来命名子类型,例如StaticAddressService、JpaAddressService、ExternalHttpAddressService 等。另外,不要像硬编码的 new 调用那样使用 JNDI,最好将这些调用放入初始化代码中,实际上将其转换/合并为一个整洁的代码, DI的标准情况。
【解决方案2】:

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

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

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

幸运的是,您的应用程序使用的初始上下文必须来自正在运行的 JBoss 实例。查看another question 的答案引用的this article 后,我能够创建自己的初始上下文,并使用自己的数据源对象填充它。

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

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 方法中发生。

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

【讨论】:

    【解决方案3】:

    如果您使用的是 Git 和 Maven 等工具,则可以使用它们轻松完成。签入 UnitTest 特定的属性文件以及边开发和 qa。使用 Maven 及其profile 工具来指定一个配置文件,将您的 UnitTest 文件复制到它应该去的地方,当在不同配置文件活动的情况下运行时,与您的 dev 和 qa 相同。

    这没有什么魔力; Spring 引入的复杂性比什么都重要。它绝对不会像这样引入简单性。

    【讨论】:

      【解决方案4】:

      您可以使用伪造的 InitialContext 实现来运行您的测试,该实现会从对 lookup(String) 的调用中返回您需要的任何内容。

      JMockit 是一个允许这种虚假实现的模拟/伪造工具。伪造的实现将如下所示:

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

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-01-09
        • 2012-01-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-23
        • 2013-05-15
        相关资源
        最近更新 更多