【问题标题】:SQL assert - compare two SQL queries in unit testsSQL 断言 - 比较单元测试中的两个 SQL 查询
【发布时间】:2015-06-16 07:34:17
【问题描述】:

我正在寻找一种在单元测试中比较两个 MySQL 查询的方法。你知道任何允许这样做的库(所有这些断言都应该通过):

SQLAssert.assertEquals("select id, name from users", "select id,     name from users") 
SQLAssert.assertEquals("select id, name from users", "select `id`,`name` from `users`")

【问题讨论】:

  • 你的意思是查询应该返回相同的结果吗?
  • @a_horse_with_no_name 我从问题中删除了这个查询
  • @NickJ 是的,它们应该返回相同的结果,但我想知道不运行它们

标签: java mysql sql unit-testing


【解决方案1】:

虽然针对内存数据库运行查询并比较结果是最好的答案,但我认为有一些不太全面且更脆弱的选项仍然有用。

实际上,您可以对查询的语法施加额外的限制。在您的示例中,只有选择语句、单个表、没有 where 子句,唯一的查询差异是反引号和空格,因此编写一个使用这些约束规范化查询的方法可能是可行的。比如:

private String normalize(String str) {
    return str.replaceAll(" +", " ").replaceAll("`", "");
}

然后可以比较这些规范化的字符串。这种做事方式非常脆弱(因此不是未来的证明),但这并不意味着它在某些情况下不能提供价值。当然,有相当多的有效 sql 语句会导致它中断,但您不必处理有效 sql 所需的完整字符串集。您只需要处理查询使用的任何 sql 子集。

如果您的查询差异足以使此代码不合理,则使用 JSqlParser 之类的解析器库来解析各个部分然后导航结构进行比较可能会更容易。同样,您不必支持所有 SQL,只要查询使用的任何子集即可。此外,测试不必测试完整的逻辑等价性才有用。测试可能只是确保两个查询中提到的所有表都是相同的,而不管连接和排序如何。这并没有使它们等效,但它确实可以防止特定类型的错误并且比没有更有用。

如果您在查询构建器上进行大量重构,并且希望确保最终查询是等效的,这可能会很有用。在这种情况下,您不是在测试查询本身,而是在构建查询。

我不建议将此作为单元测试中的常规做法,但我认为它在非常特殊的情况下很有用。

【讨论】:

    【解决方案2】:

    您可以使用 JSqlParser 来解析您的查询。然后,您可以使用 JSqlParser 的所谓 Deparser 来获取您的 SQL 版本,而无需额外的空格、制表符、换行符。从这里开始,您可以使用简单的字符串相等检查。当然,您必须处理各种引号,例如 " 或 [] 但它可以工作,就像示例代码所示。

    这不起作用,引用开始起作用或您的 SQL 中的列或表达式的不同顺序。引用问题很容易通过表达式解析器的扩展来解决。

    Statement stmt1 = CCJSqlParserUtil.parse("select id, name from users");
    Statement stmt2 = CCJSqlParserUtil.parse("select id,     name from users");       
    Statement stmt3 = CCJSqlParserUtil.parse("select `id`,`name` from `users`");       
    
    //Equality 
    System.out.println(stmt1.toString().equals(stmt2.toString()));
    
    ExpressionDeParser exprDep = new ExpressionDeParser() {
        @Override
        public void visit(Column tableColumn) {
            tableColumn.setColumnName(tableColumn.getColumnName().replace("`", ""));
            super.visit(tableColumn);
        }
    };
    SelectDeParser stmtDep = new SelectDeParser() {
        @Override
        public void visit(Table tableName) {
            tableName.setName(tableName.getName().replace("`", ""));
            super.visit(tableName);
        }  
    };
    exprDep.setBuffer(stmtDep.getBuffer());
    stmtDep.setExpressionVisitor(exprDep);
    
    ((Select)stmt3).getSelectBody().accept(stmtDep);
    String stmt3Txt = stmtDep.getBuffer().toString();
    
    System.out.println(stmt1.toString().equals(stmt3Txt));
    

    【讨论】:

      【解决方案3】:

      我建议断言 2 个查询返回相同结果的唯一方法是实际运行它们。当然,您想要做的是将单元测试连接到真实的数据库。造成这种情况的原因有很多:

      1. 测试会影响数据库中的内容,您不希望将大量测试数据引入生产数据库

      2. 每个测试都应该是自包含的,并且每次运行时都以相同的方式工作,这要求数据库在每次运行开始时处于相同的已知状态。这需要对每个测试进行重置 - 与生产(或开发环境)数据库无关。

      考虑到这些限制,我建议您查看DBUnit,它专为数据库驱动的 JUnit 测试而设计。我还建议,不要使用 MySQL 进行单元测试,而是使用内存数据库(示例使用 HSQLDB),这样您就可以在没有实际持久化测试数据的情况下测试查询。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-12-04
        • 2010-11-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多