SQL断言 - 在单元测试中比较两个SQL查询

时间:2015-06-16 07:34:17

标签: java mysql sql unit-testing

我正在寻找一种在单元测试中比较两个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`")

3 个答案:

答案 0 :(得分:2)

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

在实践中,您可能会对查询的语法设置其他限制。在您的示例中,只有select语句,单个表,没有where子句,并且唯一的查询差异是反引号和空格,因此编写一个使用这些约束规范化查询的方法可能是可行的。类似的东西:

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

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

如果您的查询不同以使此代码不合理,则可能更容易使用像JSqlParser这样的解析器库来解析碎片,然后导航结构以进行比较。同样,您不必支持所有SQL,只需支持查询使用的任何子集。此外,测试不必测试完全逻辑等效是有用的。测试可能只是确保两个查询中提到的所有表都是相同的,无论连接和排序如何。这并不能使它们等效,但它确实可以防止某种特定的错误,并且比没有错误更有用。

这可能有用的情况的一个示例是,如果您在查询构建器上执行大量重构,并且您希望确保结束查询是等效的。在这种情况下,您不是自己测试查询,而是查询构建。

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

答案 1 :(得分:2)

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

这不起作用,引用发挥作用或SQL中的列或表达式的不同顺序。引用问题很容易通过扩展到表达式deparser来解决。

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));

答案 2 :(得分:0)

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

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

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

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