4.8.1. 中间件集成测试

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

为了在测试中配置和启动中间件 Spring 容器,需要在项目中创建 com.haulmont.cuba.testsupport.TestContainer 的子类,并且在测试用例中使用其实例作为 JUnit Rule。

下面是容器类和快速开始中提到的 Sales 项目的一个集成测试的示例。所有的类必须在 core 模块的 test 目录。

  1. package com.company.sales;
  2. import com.haulmont.cuba.testsupport.TestContainer;
  3. import java.util.ArrayList;
  4. import java.util.Arrays;
  5. public class SalesTestContainer extends TestContainer {
  6. public SalesTestContainer() {
  7. super();
  8. appComponents = new ArrayList<>(Arrays.asList(
  9. "com.haulmont.cuba"
  10. // add CUBA premium add-ons here
  11. // "com.haulmont.bpm",
  12. // "com.haulmont.charts",
  13. // "com.haulmont.fts",
  14. // "com.haulmont.reports",
  15. // and custom app components if any
  16. ));
  17. appPropertiesFiles = Arrays.asList(
  18. // List the files defined in your web.xml
  19. // in appPropertiesConfig context parameter of the core module
  20. "com/company/sales/app.properties",
  21. // Add this file which is located in CUBA and defines some properties
  22. // specifically for test environment. You can replace it with your own
  23. // or add another one in the end.
  24. "com/haulmont/cuba/testsupport/test-app.properties");
  25. initDbProperties();
  26. }
  27. private void initDbProperties() {
  28. dbDriver = "org.postgresql.Driver";
  29. dbUrl = "jdbc:postgresql://localhost/sales_test";
  30. dbUser = "cuba";
  31. dbPassword = "cuba";
  32. }
  33. public static class Common extends SalesTestContainer {
  34. // A common singleton instance of the test container which is initialized once for all tests
  35. public static final SalesTestContainer.Common INSTANCE = new SalesTestContainer.Common();
  36. private static volatile boolean initialized;
  37. private Common() {
  38. }
  39. @Override
  40. public void before() throws Throwable {
  41. if (!initialized) {
  42. super.before();
  43. initialized = true;
  44. }
  45. setupContext();
  46. }
  47. @Override
  48. public void after() {
  49. cleanupContext();
  50. // never stops - do not call super
  51. }
  52. }
  53. }

自定义 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. package com.company.sales;
  2. import com.company.sales.entity.Customer;
  3. import com.haulmont.cuba.core.global.*;
  4. import org.junit.Before;
  5. import org.junit.ClassRule;
  6. import org.junit.Test;
  7. import static org.junit.Assert.assertEquals;
  8. import static org.junit.Assert.assertNotNull;
  9. public class CustomerTest {
  10. // Using the common singleton instance of the test container which is initialized once for all tests
  11. @ClassRule
  12. public static SalesTestContainer cont = SalesTestContainer.Common.INSTANCE;
  13. private Metadata metadata;
  14. @Before
  15. public void setUp() throws Exception {
  16. metadata = cont.metadata();
  17. }
  18. @Test
  19. public void testCreateCustomer() throws Exception {
  20. // Get a managed bean (or service) from container
  21. DataManager dataManager = AppBeans.get(DataManager.class);
  22. // Create new Customer
  23. Customer customer = metadata.create(Customer.class);
  24. customer.setName("Test customer");
  25. // Save the customer to the database
  26. dataManager.commit(customer);
  27. // Load the customer by ID
  28. Customer loaded = dataManager.load(
  29. LoadContext.create(Customer.class).setId(customer.getId()).setView(View.LOCAL));
  30. assertNotNull(loaded);
  31. assertEquals(customer.getName(), loaded.getName());
  32. // Remove the customer
  33. dataManager.remove(loaded);
  34. }
  35. }

几个有用的测试容器方法

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 {
    2. static {
    3. System.setProperty("logback.configurationFile", "my-test-logback.xml");
    4. }
    5. // ...
    6. }

附加数据存储

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

  1. public class MyTestContainer extends TestContainer {
  2. // ...
  3. @Override
  4. protected void initDataSources() {
  5. super.initDataSources();
  6. try {
  7. Class.forName("org.postgresql.Driver");
  8. TestDataSource mydbDataSource = new TestDataSource(
  9. "jdbc:postgresql://localhost/mydatabase", "db_user", "db_password");
  10. TestContext.getInstance().bind(
  11. AppContext.getProperty("cuba.dataSourceJndiName_mydb"), mydbDataSource);
  12. } catch (ClassNotFoundException | NamingException e) {
  13. throw new RuntimeException("Error initializing datasource", e);
  14. }
  15. }
  16. }

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

  1. configure(coreModule) {
  2. // ...
  3. dependencies {
  4. // ...
  5. testRuntime(hsql)
  6. jdbc('org.postgresql:postgresql:9.4.1212')
  7. testRuntime('org.postgresql:postgresql:9.4.1212') // add this
  8. }