如何对 seed/clean 数据库和 运行 测试使用不同的数据源?

How to use different datasources to seed/clean database and running the tests?

我们正在尝试为我们的项目设置 Arquillian 运行 自动测试。我们想利用 arquillian 持久性扩展来编写使用持久层的测试。所以我们想使用 @UsingDataSet and/or @CreateSchema@ 注释来为数据库做种。

我们所有的应用程序组件都有自己的数据库用户,这些用户只能访问组件需要的那些 tables/attributes。 None 个组件有权执行删除或 DDL 语句。所以我们需要在数据库 user/datasource seed/clean 模式 before/after 测试和执行测试之间切换,如下所示:

  1. 种子数据库,使用数据源 A 删除和重新创建序列
  2. 运行 使用数据源 B 的测试
  3. 使用数据源 A 清理数据库

很明显,如果我们将所需的 delete/DDL-rights 授予组件数据库用户以进行 arquillian 测试,则根据定义测试结果将不可靠。

那么我们如何使用 arquillian.xml 中定义的不同数据源到 seed/clean 数据库和 运行 测试?

目前还不支持(我们正在努力),但是在版本 2.0.0(目前处于 alpha 阶段,在 maven central 上)有一个解决方法,使用编程方式而不是声明方式(使用注释)。您可以在此处查看示例 https://github.com/arquillian/arquillian-extension-persistence/blob/2.0.0/arquillian-ape-sql/container/int-tests/src/test/java/org/arquillian/integration/ape/dsl/ApeDslIncontainerTest.java

我访问过的 arquillian 培训课程的讲师提到,为 EJB 的 seeding/cleaning 和 PersistenceContext 定义不同的数据源应该没有问题。所以我坐下来测试一下。

TL;DR: It's possible to just use two different datasources.

这是我的测试设置和测试结果。

本地测试设置

数据库

作为数据库,我安装了一个 Oracle XE,因为我们在公司使用 Oracle 数据库。由于组件的数据库用户没有自己的架构,而是访问架构所有者的 table,我创建了三个数据库用户:

  • 用户“bish”是模式“bish”的模式所有者,其中包含 empty table 我在测试中使用的“Emp”
  • 用户“readinguser”获得了 table“bish.Emp”
  • 的“SELECT、INSERT、UPDATE”权限
  • 用户“writinguser”获得了 table“bish.Emp”
  • 的“SELECT、INSERT、UPDATE、DELETE”权限

应用服务器

作为应用程序服务器,我使用了一个 Wildfly 10.x 并定义了两个数据源,一个用于我的两个测试用户

<datasource jndi-name="java:/ReadingDS" pool-name="ReadingDS" enabled="true">
  <connection-url>jdbc:oracle:thin:@localhost:1521:xe</connection-url>
  <driver>oracle</driver>
  <pool>
    <min-pool-size>1</min-pool-size>
    <max-pool-size>5</max-pool-size>
    <prefill>true</prefill>
  </pool>
  <security>
    <user-name>readinguser</user-name>
    <password>oracle</password>
  </security>
</datasource>

<datasource jndi-name="java:/WritingDS" pool-name="WritingDS" enabled="true">
  <connection-url>jdbc:oracle:thin:@localhost:1521:xe</connection-url>
  <driver>oracle</driver>
  <pool>
    <min-pool-size>1</min-pool-size>
    <max-pool-size>5</max-pool-size>
    <prefill>true</prefill>
  </pool>
  <security>
    <user-name>writingguser</user-name>
    <password>oracle</password>
    </security>
</datasource>

测试应用程序

然后我写了一个带有实体的小应用程序,还有EJB,persistence.xml,arquillian.xml,数据集和测试class

实体(仅显示 table 具有显式模式命名的定义)

@Entity
@Table(name = "Emp", schema = "bish")
public class Emp implements Serializable {
  // Straight forward entity...
}

具有两种方法的 EJB select创建和删除所有条目

@Stateless
@Remote(IEmpService.class)
@LocalBean
public class EmpService implements IEmpService {

  @PersistenceContext
  private EntityManager em;

  public void removeAllEmps() {
    em.createQuery("DELETE FROM Emp").executeUpdate();
  }

  public List<Emp> getAllEmps() {
    return em.createQuery("FROM Emp", Emp.class).getResultList();
  }
}

persistence.xml 内的持久性单元使用 EJB 内的“ReadingDS”

<persistence-unit name="ReadingUnit" transaction-type="JTA">
  <provider>org.hibernate.ejb.HibernatePersistence</provider>

  <jta-data-source>java:/ReadingDS</jta-data-source>
  <shared-cache-mode>NONE</shared-cache-mode>
</persistence-unit>

Arquillian.xml 使用“WritingDS”的定义 seed/clean table 和模式定义

<extension qualifier="persistence">
  <property name="defaultDataSeedStrategy">CLEAN_INSERT</property>
  <property name="defaultCleanupStrategy">USED_ROWS_ONLY</property>
  <property name="defaultDataSource">java:/WritingDS</property>
</extension>

<extension qualifier="persistence-dbunit">
    <property name="schema">bish</property>
</extension> 

测试中使用的数据集“empBefore.xml”class

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
  <EMP EMPNO="9998" ENAME="TEst" JOB="Eins" HIREDATE="1982-01-23" SAL="1300" DEPTNO="10"/>
  <EMP EMPNO="9999" ENAME="Test" JOB="Zwei" MGR="9998" HIREDATE="1982-01-23" SAL="1300" DEPTNO="10"/>
</dataset>

测试class:

@RunWith(Arquillian.class)
public class DataSourceTest {

  @Deployment
  public static JavaArchive createDeployment() {
    // ...
  }

  @EJB
  EmpService testclass;

  @Rule
  public ExpectedException thrown = ExpectedException.none();

  @UsingDataSet("empBefore.xml")
  @Test
  public void GetAllEmps() {
    List<Emp> allEmps = testclass.getAllEmps();

    Assert.assertEquals(2, allEmps.size());
  }

  @UsingDataSet("empBefore.xml")
  @Test
  public void DeleteAllEmps() {
    thrown.expect(EJBException.class);
    thrown.expectCause(CoreMatchers.isA(PersistenceException.class));

    testclass.removeAllEmps();
  }

}

测试

我首先执行了 GetAllEmps 测试方法来查看 table 是否正确地使用了 DataSet 的数据以及 EJB 的 select 方法作品。在我第一次执行时,我得到了以下异常。 (很抱歉发布了这么多文字,但这很重要,请参见下文!)

19:15:51,553 WARN [com.arjuna.ats.arjuna] (default task-38) ARJUNA012140: Adding multiple last resources is disallowed. Trying to add LastResourceRecord(XAOnePhaseResource(LocalXAResourceImpl@666ebccc[connectionListener=11852abe connectionManager=3f58cd97 warned=false currentXid=< formatId=131077, gtrid_length=29, bqual_length=36, tx_uid=0:ffffc0a80002:d99c90f:59971e1c:4c, node_name=1, branch_uid=0:ffffc0a80002:d99c90f:59971e1c:50, subordinatenodename=null, eis_name=java:/ReadingDS > productName=Oracle productVersion=Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production jndiName=java:/ReadingDS])), but already have LastResourceRecord(XAOnePhaseResource(LocalXAResourceImpl@6027d87b[connectionListener=41a0034d connectionManager=329cdd5f warned=false currentXid=< formatId=131077, gtrid_length=29, bqual_length=36, tx_uid=0:ffffc0a80002:d99c90f:59971e1c:4c, node_name=1, branch_uid=0:ffffc0a80002:d99c90f:59971e1c:4e, subordinatenodename=null, eis_name=java:/WritingDS > productName=Oracle productVersion=Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production jndiName=java:/WritingDS]))

19:15:51,554 WARN [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-38) SQL Error: 0, SQLState: null

19:15:51,554 ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-38) javax.resource.ResourceException: IJ000457: Unchecked throwable in managedConnectionReconnected() cl=org.jboss.jca.core.connectionmanager.listener.TxConnectionListener@11852abe[state=NORMAL managed connection=org.jboss.jca.adapters.jdbc.local.LocalManagedConnection@7fc47256 connection handles=0 lastReturned=1503076551554 lastValidated=1503075869230 lastCheckedOut=1503076551553 trackByTx=false pool=org.jboss.jca.core.connectionmanager.pool.strategy.OnePool@6893c4c mcp=SemaphoreConcurrentLinkedQueueManagedConnectionPool@61e62cb9[pool=ReadingDS] xaResource=LocalXAResourceImpl@666ebccc[connectionListener=11852abe connectionManager=3f58cd97 warned=false currentXid=null productName=Oracle productVersion=Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production jndiName=java:/ReadingDS] txSync=null]

19:15:51,554 ERROR [org.jboss.as.ejb3.invocation] (default task-38) WFLYEJB0034: EJB Invocation failed on component EmpService for method public java.util.List de.test.EmpService.getAllEmps(): javax.ejb.EJBTransactionRolledbackException: org.hibernate.exception.GenericJDBCException: Unable to acquire JDBC Connection

感谢 this SO-Question 我可以通过在我的 wildfly 中设置以下系统属性 来解决问题:

 <system-properties>
     <property name="com.arjuna.ats.arjuna.allowMultipleLastResources" value="true"/>
 </system-properties>

关于此异常最重要的一点是 wildfly 尝试创建两个连接,一个用于异常文本中的每个数据源(请参阅突出显示的 JNDI 名称)。在设置 system-属性 之前,我通过删除 @UsingDataSet-Annotation 验证了这一点。删除测试用例后失败,因为断言 (Assert.assertEquals(2, allEmps.size());) 失败,因为 table 中有零行 - 这表明没有为播种创建第二个连接。所以我创建了系统 属性,使用了 DataSet 并得到了一个绿色条。

第二个测试方法试图删除集合中的所有条目,这必须在异常中失败,因为 readingDS 数据源背后的用户无权删除 table 中的行。这个测试也成功了。完整的异常日志是:

javax.ejb.EJBTransactionRolledbackException: org.hibernate.exception.SQLGrammarException: could not execute statement

[...]

Caused by: javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not execute statement

[...]

... 187 more

Caused by: org.hibernate.exception.SQLGrammarException: could not execute statement

[...]

... 217 more

Caused by: java.sql.SQLSyntaxErrorException: ORA-01031: insufficient privileges

[...]

... 226 more

如您所见,由于权限不足,删除语句失败

结论

通过在 arquillian.xml 和 EJB 的持久性单元内定义数据源,可以使用与 seed/delete table 不同的数据源。 Arquillian 和应用服务器可以正确处理这些不同的数据源。