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

时间:2013-02-07 11:46:43

标签: unit-testing playframework playframework-2.0 database-testing

我想创建单元测试,其中包含在Play框架2.1.0中使用关系数据库的代码。这有很多可能性,并且都会导致问题:

在内存H2数据库上进行测试

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

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

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

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

任何人都使用这种方法(使evolutions与MySQL和H2兼容)?

其他想法如何处理:

  • MySQL和H2的单独演变(不是一个好主意)
  • 某些方法让H2忽略了create table中的其他MySQL内容(MySQL兼容模式不起作用,它仍然会在default character set上抱怨)。我不知道怎么做。

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

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

如何在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);

看起来evolution在这里工作,但在每次测试之前如何最好清理数据库?通过创建截断每个表的自定义代码?如果它会丢弃表格,那么演化会在下一次测试之前再次运行,还是每play test个命令应用一次?或者每Helpers.fakeApplication()次调用一次?

这里的最佳做法是什么?听说dbunit,是否有可能在没有太多痛苦和怪癖的情况下整合它?

5 个答案:

答案 0 :(得分: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创建,避免使用一些样板代码。

一些有用的链接:

答案 1 :(得分: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>

使用此方法时,Evolutions会自动在测试数据库上运行,因此如果从测试数据库中删除所有表,则会重新创建它们。

如果您只需要清理表,则使用dbunit是一种过度的做法,您可以通过直接发出查询或使用ebean DdlGenerator来清除它们。但我也使用dbunit来比较数据。

我不使用Helpers.running,因为RunnableRunnable实现不能抛出异常 - 对测试非常不方便。但是,如果您查看running()的代码,则只需调用Helpers.start()Helpers.stop(),因此我直接在@Before@After中调用这些方法。

决定不使用H2进行运行测试:是的,运行速度更快,但它与MySQL之间存在太大差异。

答案 2 :(得分:1)

  

任何人都使用这种方法(使evolutions与MySQL和H2兼容)?

我找到了MySQL特定功能的答案:How can I unit test for MySQL database with Play 2.x?

答案 3 :(得分: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
        }
    });
}

答案 4 :(得分:1)

你也可以使用DB mock,如果目标是验证你的Slick | JPA | Anorm映射和&amp;基于。的功能。

当它适合时,它更有利于单元测试而不是测试数据库,并且更易于管理(不是设置/清除任务,不是同步测试以避免访问相同的测试表)。

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