3

如何在结束顺序相当灵活的情况下对生成的字符串进行单元化。假设我正在尝试测试一些代码,这些代码打印出来自键值对的生成 SQL。但是,许多片段的确切顺序并不重要。

例如

SELECT
    *
FROM
    Cats
WHERE
    fur = 'fluffy'
OR
    colour = 'white'

在功能上等同于

SELECT
    *
FROM
    Cats
WHERE
    colour = 'white'
OR
    fur = 'fluffy'

生成条件子句的顺序无关紧要,但重要的是它们遵循 where 子句。此外,很难预测,因为循环 a 时对的排序entrySet()HashMap不可预测的。对键进行排序可以解决这个问题,但会因为没有(或负)业务价值而引入运行时惩罚。

如何在不过度指定顺序的情况下对此类字符串的生成进行单元测试?

我考虑过使用正则表达式,但* 我想不出如何写一个说:

一个正则表达式是我在想的,但我可以想到一个正则表达式,上面写着“SELECT * FROM Cats WHERE”,然后是 {“fur = 'fluffy', color = 'white'} 之一,然后是“OR”followed by one of one of {"fur = 'fluffy',颜色= 'white'} ...而不是上次使用的那个。

注意:我实际上并没有使用 SQL 来执行此操作,它只是为解决问题提供了一种更简单的方法。

4

4 回答 4

4

我看到了一些不同的选择:

如果您可以忍受适度的运行时损失,请LinkedHashMap保持插入顺序。

如果您想在不更改实现的情况下完全解决这个问题,在您的示例中,我不明白为什么您应该做一些比检查每个片段是否出现在代码中以及它们出现在WHERE. 伪代码:

Map<String, String> parametersAndValues = { "fur": "fluffy", "colour", "white" };
String generatedSql = generateSql(parametersToValues);
int whereIndex = generatedSql.indexOf("WHERE");
for (String key, value : parametersAndValues) {
    String fragment = String.format("%s = '%s'", key, value);
    assertThat(generatedSql, containsString(fragment));
    assertThat(whereIndex, is(lessThan(generatedSql.indexOf(fragment))));
}

但我们可以做的比这更简单。由于您实际上不必使用大量参数来测试它——对于大多数实现来说,只有三个重要的量,“无、一个或多个”——实际上可以根据所有可能的值对其进行测试:

String variation1 = "SELECT ... WHERE fur = 'fluffy' OR colour = 'white'";
String variation2 = "SELECT ... WHERE colour = 'white' OR fur = 'fluffy'";
assertThat(generatedSql, is(anyOf(variation1, variation2)));

编辑:为了避免手工编写所有可能的变化(如果你有两个或三个以上的项目,这会变得相当乏味,因为有n!组合n 个项目的方法),你可以看看生成所有可能排列的算法一个序列并执行以下操作:

List<List<String>> permutations = allPermutationsOf("fur = 'fluffy'", 
    "colour = 'white'", "scars = 'numerous'", "disposition = 'malignant'");
List<String> allSqlVariations = new ArrayList<>(permutations.size());
for (List<String> permutation : permutations) {
    allSqlVariations.add("SELECT ... WHERE " + join(permutation, " OR "));
}
assertThat(generatedSql, is(anyOf(allSqlVariations)));
于 2013-01-03T16:12:51.590 回答
1

好吧,一种选择是以某种方式解析 SQL,提取字段列表并检查一切是否正常,而不考虑字段的顺序。然而,这将是相当难看的:如果做得好,你必须实现一个完整的 SQL 解析器(显然是矫枉过正),如果你使用正则表达式或类似的快速而肮脏的方法,你可能会冒着测试会中断的风险对生成的 SQL 的更改。

相反,我建议结合使用单元测试和集成测试:

  • Have a unit test that tests the code which supplies the list of fields for building the SQL. I.e., have a method Map getRestrictions() which you can easily unit-test.
  • Have an integration test for the SQL generation as a whole, which runs against a real database (maybe some embedded DB like the H2 database, which you can a start just for the test).

That way, you unit-test the actual values supplied to the SQL, and you integration-test that you are really creating the right SQL.

注意:我认为这是“集成代码”的一个示例,无法进行有用的单元测试。问题是代码本身并不能产生真实的、可测试的结果。相反,它的目的是与产生结果的数据库进行交互(通过向其发送 SQL)。换句话说,代码做正确的事情不是如果它产生一些特定的 SQL 字符串,而是如果它驱动数据库做正确的事情。因此,该代码只能在数据库中进行有意义的测试,即在集成测试中。

于 2013-01-03T16:20:41.873 回答
0

首先,使用 aLinkedHashMap而不是常规的HashMap。它不应该引入任何明显的性能下降。它保留了插入顺序,而不是排序。

其次,以易于理解的方式将这些对插入到地图中。也许您正在从表中获取数据,并且添加排序索引是不可接受的。但也许数据库可以按主键或其他东西排序。

结合起来,这两个变化应该会给你可预测的结果。

或者,使用比字符串 equals 更智能的方法比较实际与预期。也许是一个正则表达式来清除所有注入到实际 SQL 查询中的对?

于 2013-01-03T15:48:54.230 回答
-2

到目前为止,我想出的最好的方法是在测试期间使用一些库(就像 PowerMockito 一样)HashMapSortedMaplike替换TreeMap。这样,对于测试,顺序将是固定的。但是,这仅适用于地图不是在生成字符串的相同代码中构建的情况。

于 2013-01-03T15:39:30.660 回答