如何在Play框架中创建针对非内存数据库(例如MySQL)的单元测试,并重置到已知状态?

23

我想创建单元测试,覆盖在Play框架2.1.0中使用关系型数据库的代码。有许多可能性可供选择,但都会导致问题:

在内存中测试H2数据库

Play框架文档建议在H2内存数据库上运行单元测试,即使开发和生产中使用的主数据库是其他软件(例如MySQL):

app = Helpers.fakeApplication(Helpers.inMemoryDatabase());

我的应用程序不使用复杂的RDBMS功能,例如存储过程,大多数数据库访问情况都是基于ebean调用的,因此它应与MySQL和H2兼容。

但是,在演变中的表创建语句中使用了MySQL特定的功能,例如指定ENGINE = InnoDBDEFAULT CHARACTER SET = utf8等。我担心如果我删除这些专有部分的CREATE TABLE,MySQL将使用一些我无法控制且取决于版本的默认设置,因此为了测试和开发应用程序,必须修改主MySQL配置。

有人使用过这种方法(使演化兼容于MySQL和H2)吗?

其他处理方法:

  • 为MySQL和H2分别创建演化文件(不是一个好方法)
  • 某种方法使H2忽略create table中的其他MySQL内容(MySQL兼容模式不起作用,即使在default character set上也会报错)。 我不知道如何做。

在与主数据库相同的数据库驱动程序上进行测试

H2内存数据库唯一的优点是速度较快,并且在与开发/生产数据库相同的数据库驱动程序上进行测试可能更好,因为它更接近真实环境。

在Play框架中如何正确操作?

尝试过:

Map<String, String> settings = new HashMap<String, String>();
settings.put("db.default.url", "jdbc:mysql://localhost/sometestdatabase");
settings.put("db.default.jndiName", "DefaultDS");
app = Helpers.fakeApplication(settings);

看起来演变在这里运作,但在每次测试之前最好如何清除数据库?通过创建自定义代码截断每个表格吗?如果它将删除表格,那么演变会在下次测试之前再次运行,还是只应用于每个play test命令?或只应用于每个Helpers.fakeApplication()调用?

在这里有什么最佳实践?听说过dbunit,是否可能在没有太多痛苦和问题的情况下集成它?


你在这方面有什么进展了吗? - uthomas
5个回答

8

首先,我建议您在测试和生产中使用相同的RDBMS,这样可以避免一些难以发现的错误。

关于每个测试之间需要清理数据库的需求,您可以使用Ebean的DdlGenerator生成脚本来创建一个干净的数据库,并使用JUnit的@Before注释来在每个测试之前自动执行这些脚本。

使用DdlGenerator可以像这样完成:

    EbeanServer server = Ebean.getServer(serverName);
    ServerConfig config = new ServerConfig();
    DdlGenerator ddl = new DdlGenerator((SpiEbeanServer) server, new MySqlPlatform(), config);

这段代码可以放在一个基类中,你可以让你的测试继承该基类(或者放在一个自定义Runner内使用注解@RunWith)。

这也能够让你轻松地自动创建FakeApplication,避免了一些样板代码。

以下链接可能会有所帮助:


你的代码是否使用与主应用程序相同的数据库来运行测试,或者在application.conf中有一些设置可以使测试自动使用单独的数据库? - kolen
@mguillermin,您能否看一下这个问题?https://dev59.com/VXrZa4cB1Zd3GeqP1FRp - pmichna
@pmichna,我已经回答了你的问题。 - mguillermin
@mguillermin,我能在真正的Play框架项目中使用外部数据库吗?因为我听说2.0除了H2之外不支持其他数据库。 - Kyle Luke

5

在每次测试之前,我使用了与主数据库相同的数据库引擎和dbunit来进行清理。

public class SomeTest {
    // ...

    @Before
    public void startApp() throws Exception {
        // Set up connection to test database, different from main database. Config better should be used instead of hard-coding.
        Map<String, String> settings = new HashMap<String, String>();
        settings.put("db.default.url", "jdbc:mysql://localhost/somedatabase?characterEncoding=UTF-8&useOldAliasMetadataBehavior=true");
        settings.put("db.default.user", "root");
        settings.put("db.default.password", "root");
        settings.put("db.default.jndiName", "DefaultDS"); // make connection available to dbunit through JNDI
        app = Helpers.fakeApplication(settings);
        Helpers.start(app);

        databaseTester = new JndiDatabaseTester("DefaultDS");

        IDataSet initialDataSet = new FlatXmlDataSetBuilder().build(play.Play.application()
                .resourceAsStream("/resources/dataset.xml"));
        databaseTester.setDataSet(initialDataSet);
        databaseTester.onSetup();
    }

    @After
    public void stopApp() throws Exception {
        databaseTester.onTearDown();
        Helpers.stop(app);
    }
}

我的 dataset.xml 只包含表的名称,以便通知 dbunit 在每个测试之前清空这些表。它也可以包含固定内容。
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
  <name_of_my_first_table />
  <name_of_my_second_table />
</dataset>

使用这种方法时,测试数据库上的进化会自动运行,因此,如果您从测试数据库中删除所有表格,它们将被重新创建。
如果您只需要清空表格,使用dbunit就过于冗余了,您可以通过直接发出查询或使用ebean DdlGenerator来清空它们。但是我也使用dbunit进行数据比较。
我不使用Helpers.running,因为它需要Runnable,而Runnable实现无法抛出异常-对于测试非常不方便。但是,如果您查看running()的代码,它只调用Helpers.start()和Helpers.stop(),因此我在@Before和@After中直接调用这些方法。
决定不使用H2来运行测试:是的,它运行得更快,但它与MySQL之间存在太多差异。

1
当我为我的Postgres数据库编写测试时,我只需创建一个HashMap来连接数据库,然后编写测试查询以确保正确的记录数量存在等等... 这是我的代码。
    @Test
public void testDataBase() {
    final HashMap<String,String> postgres = new HashMap<String, String>();
    postgres.put("db.default.driver","org.postgresql.Driver");
    postgres.put("db.default.url","jdbc:postgresql://localhost/myDataBase");
    postgres.put("db.default.user", "postgres");
    postgres.put("db.default.password", "password");

    running(fakeApplication(postgres), new Runnable() {

        @Override
        public void run() {

            //Insert Assertions Here
        }
    });
}

1

1

如果您的目标是验证Slick|JPA|Anorm映射和函数,您也可以使用DB模拟。

当它适合时,与测试数据库相比,它具有更好的单元测试兼容性,并且更易于管理(无需设置/清除任务,无需同步测试以避免访问相同的测试表)。

您可以查看我的框架Acolyte ( http://github.com/cchantep/acolyte ),它被用于Anorm本身的规范中(例如https://github.com/playframework/playframework/blob/master/framework/src/anorm/src/test/scala/anorm/SqlResultSpec.scala)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接