Java 如何在顺序无关紧要的情况下测试生成的字符串?

Java 如何在顺序无关紧要的情况下测试生成的字符串?,java,string,unit-testing,Java,String,Unit Testing,如何在结束顺序相当灵活的情况下对生成的字符串进行单元化。假设我正在测试一些代码,这些代码打印出从键值对生成的SQL。然而,许多碎片的确切顺序并不重要 比如说 SELECT * FROM Cats WHERE fur = 'fluffy' OR colour = 'white' 在功能上与相同 SELECT * FROM Cats WHERE colour = 'white' OR fur = 'fluffy' 条件子句的生成顺序

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

比如说

SELECT
    *
FROM
    Cats
WHERE
    fur = 'fluffy'
OR
    colour = 'white'
在功能上与相同

SELECT
    *
FROM
    Cats
WHERE
    colour = 'white'
OR
    fur = 'fluffy'
条件子句的生成顺序并不重要,但它们遵循where子句才是重要的。而且,这很难预测,因为当循环通过
HashMap
entrySet()
时,对的顺序是不可预测的。对键进行排序可以解决这一问题,但会对无(或负)业务值引入运行时惩罚

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

我曾想过使用regexp,但我想不出如何编写这样一个:


一个正则表达式是我一直在想的,但我能想到一个正则表达式,它说的是类似于“从猫身上选择*”,后面跟着一个{“毛皮='fluffy',color='white'},后面跟着一个“或”
后面跟着一个{“毛皮='fluffy',
color='white'}。。。而不是上次用的那个


NB:我实际上并没有用SQL来做这件事,它只是为解决问题提供了一种更简单的方法。

到目前为止,我想到的最好的方法是在测试期间使用一些库(像PowerMockito一样糟糕)用
SortedMap
替换
HashMap
。这样,对于测试,顺序将是固定的。但是,这仅在映射不是在生成字符串的同一代码中生成的情况下才有效。

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

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

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


或者,使用比字符串相等更智能的方法比较实际值和预期值。可能需要一个正则表达式来清除所有注入到实际SQL查询中的对?

我看到了几个不同的选项:

如果您可以承受适度的运行时惩罚,请保持插入顺序

如果您想在不改变实现的情况下完全解决这个问题,那么在您的示例中,我不明白为什么您需要做比检查每个片段是否出现在代码中以及它们是否出现在
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))));
}
编辑:为了避免手工编写所有可能的变体(如果您有两个或三个以上的项目,这会变得相当乏味,因为有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)));
List permutations=allPermutationsOf(“毛发='fluffy'”,
“颜色=‘白色’”,“疤痕=‘大量’,“性情=‘恶性’”;
List allSqlVariations=newarraylist(permutations.size());
for(列表置换:置换){
add(“SELECT…WHERE”+join(permutation)或“);
}
断言(generatedSql,is(anyOf(allSqlVariations));

好吧,一个选项是以某种方式解析SQL,提取字段列表,检查一切是否正常,而不考虑字段的顺序。然而,这将是相当难看的:如果做得正确,您必须实现一个完整的SQL解析器(显然是过火了),如果您使用regex或类似的工具快速而肮脏地实现它,那么您就有可能因为对生成的SQL的微小更改而中断测试

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

  • 有一个单元测试,用于测试为构建SQL提供字段列表的代码。也就是说,有一个方法
    Map getRestrictions()
    ,您可以轻松地进行单元测试
  • 作为一个整体,对SQL生成进行集成测试,该测试针对一个真实的数据库(可能是一些嵌入式数据库,如H2数据库,您可以从测试开始)
这样,您就可以对提供给SQL的实际值进行单元测试,并进行集成测试,以确定您确实在创建正确的SQL


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

而且我真的没有看到任何问题。@RohitJain我认为这个问题是含蓄而明显的,但我更直截了当地说是为了那些跟不上的人。正则表达式是我所想的,但我能想到一个正则表达式,它说的是类似于“从猫中选择”,其中“后跟一个{
”毛发='fluffy'
颜色='white'}后跟一个
”或“`后跟一个{
”毛发=‘蓬松’
,‘颜色=‘白色’}。。。而不是上次使用的方法。这个想法是用一个正则表达式来找到每一对。所以断言基本上会使用正则表达式来提取成对的映射,并将其与预期的映射进行比较。有人能解释为什么这会被否决吗?这与前面的另一个答案类似,我想这是因为在运行测试之前会更改测试代码。严格地说,这不是,测试是关于…;)@brimborium我想从一个最纯粹的角度来看,这是真的,但否决票似乎很苛刻,你是否发布了
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)));