4.8.2. 中间件集成测试

在中间件层,可以创建运行在具有完整功能的 Spring 容器里的测试用例,而且可以连接数据库。在这些测试类里面,可以运行中间件里面各细分层的代码,比如从 ORM 层到 Service 层。

首先,在 core 模块跟 src 目录平级创建 test 目录。然后在 Studio 里面重新创建 IDE 项目文件,以便在 IDE 里面运行测试。

平台自有的 TestContainer 类可以用来作为项目里面测试容器的基类。在 core 模块的 test 目录创建一个 TestContainer 类的子类,然后在这个子类的构造方法里面,重新定义加载 应用程序组件应用程序属性以及测试数据库连接的参数,示例:

  1. public class SalesTestContainer extends TestContainer {
  2. public SalesTestContainer() {
  3. super();
  4. appComponents = Arrays.asList(
  5. "com.haulmont.cuba"
  6. // add CUBA premium add-ons here
  7. // "com.haulmont.bpm",
  8. // "com.haulmont.charts",
  9. // "com.haulmont.fts",
  10. // "com.haulmont.reports",
  11. // and custom app components if any
  12. );
  13. appPropertiesFiles = Arrays.asList(
  14. // List the files defined in your web.xml
  15. // in appPropertiesConfig context parameter of the core module
  16. "com/company/sales/app.properties",
  17. // Add this file which is located in CUBA and defines some properties
  18. // specifically for test environment. You can replace it with your own
  19. // or add another one in the end.
  20. "test-app.properties",
  21. "com/company/sales/test-app.properties");
  22. dbDriver = "org.postgresql.Driver";
  23. dbUrl = "jdbc:postgresql://localhost/sales_test";
  24. dbUser = "cuba";
  25. dbPassword = "cuba";
  26. }
  27. }

自定义 test-app.properties 文件的示例:

  1. cuba.webContextName = app-core
  2. sales.someProperty = someValue

推荐使用单独的测试数据库,可以通过 build.gradle 里面定义的 createDb 任务来创建:

  1. configure(coreModule) {
  2. ...
  3. task createTestDb(dependsOn: assemble, description: 'Creates local Postgres database for tests', type: CubaDbCreation) {
  4. dbms = 'postgres'
  5. dbName = 'sales_test'
  6. dbUser = 'cuba'
  7. dbPassword = 'cuba'
  8. }

这个测试容器应当在测试类里面作为 @ClassRule 注解指定的 JUnit 规则(rule):

  1. public class CustomerLoadTest {
  2. @ClassRule
  3. public static SalesTestContainer cont = SalesTestContainer.Common.INSTANCE;
  4. private Customer customer;
  5. @Before
  6. public void setUp() throws Exception {
  7. customer = cont.persistence().createTransaction().execute(em -> {
  8. Customer customer = cont.metadata().create(Customer.class);
  9. customer.setName("testCustomer");
  10. em.persist(customer);
  11. return customer;
  12. });
  13. }
  14. @After
  15. public void tearDown() throws Exception {
  16. cont.deleteRecord(customer);
  17. }
  18. @Test
  19. public void test() {
  20. try (Transaction tx = cont.persistence().createTransaction()) {
  21. EntityManager em = cont.persistence().getEntityManager();
  22. TypedQuery<Customer> query = em.createQuery(
  23. "select c from sales$Customer c", Customer.class);
  24. List<Customer> list = query.getResultList();
  25. tx.commit();
  26. assertTrue(list.size() > 0);
  27. }
  28. }
  29. }

在上面的例子中,测试容器只初始化了一次,但是可以给这个类所有的测试方法使用,等他们执行完之后就销毁了。

容器的初始化需要一些时间,有没有办法只创建一个测试容器但是给好几个测试类使用呢?可以通过创建一个通用的测试容器单例:

  1. public class SalesTestContainer extends TestContainer {
  2. public SalesTestContainer() {
  3. ...
  4. }
  5. public static class Common extends SalesTestContainer {
  6. public static final SalesTestContainer.Common INSTANCE = new SalesTestContainer.Common();
  7. private static volatile boolean initialized;
  8. private Common() {
  9. }
  10. @Override
  11. public void before() throws Throwable {
  12. if (!initialized) {
  13. super.before();
  14. initialized = true;
  15. }
  16. setupContext();
  17. }
  18. @Override
  19. public void after() {
  20. cleanupContext();
  21. // never stops - do not call super
  22. }
  23. }
  24. }

在测试类里使用:

  1. public class CustomerLoadTest {
  2. @ClassRule
  3. public static SalesTestContainer cont = SalesTestContainer.Common.INSTANCE;
  4. ...
  5. }
几个有用的测试容器方法




TestContainer 类包含了以下几个方法,可以在测试类里面使用(参考上面的 CustomerLoadTest 例子):



-
persistence() – 返回 Persistence 接口的引用。

-
metadata() – 返回 Metadata 接口的引用。

-
deleteRecord() – 这一组重载方法的目的是在 @After 方法里面使用,在测试完成后清理数据库。



日志




测试容器根据平台提供的 test-logback.xml 文件来配置日志。这个文件在 cuba-core-tests 工件的根目录。



可以通过以下方法配置测试的日志级别:



-
从平台的包里面拷贝 test-logback.xml 到项目 core 模块 test 根目录下,比如可以重命名为 my-test-logback.xml

-
my-test-logback.xml 里面配置 appenders 和 loggers。

-
在测试容器里面添加一段静态初始化代码,这段代码通过设置 logback.configurationFile 这个系统属性来指定日志配置文件的位置:




  1. public class MyTestContainer extends TestContainer {

    static {
    System.setProperty("logback.configurationFile", "my-test-logback.xml");
    }

    // …
    }







额外数据存储




如果项目使用了额外数据存储,需要在测试容器里创建相应的 JDBC 数据源。比如,如果有名为 mydb 的数据存储,而且是 PostgreSQL 的数据库,则需要在测试容器中添加如下代码:





  1. public class MyTestContainer extends TestContainer {
    // …

    @Override
    protected void initDataSources() {
    super.initDataSources();
    try {
    Class.forName("org.postgresql.Driver");
    TestDataSource mydbDataSource = new TestDataSource(
    "jdbc:postgresql://localhost/mydatabase", "db_user", "db_password");
    TestContext.getInstance().bind(
    AppContext.getProperty("cuba.dataSourceJndiName_mydb"), mydbDataSource);
    } catch (ClassNotFoundException | NamingException e) {
    throw new RuntimeException("Error initializing datasource", e);
    }
    }
    }






还有,如果额外的数据库类型跟主数据库不一致,需要在 build.gradlecore 模块将数据库的驱动添加到 testRuntime 依赖中。示例:





  1. configure(coreModule) {
    // …
    dependencies {
    // …
    testRuntime(hsql)
    jdbc('org.postgresql:postgresql:9.4.1212')
    testRuntime('org.postgresql:postgresql:9.4.1212') // add this
    }