我使用 Spock 框架测试我的应用程序,测试是用 Groovy 编写的。
作为一些方法评估的结果,我有一个对象列表。我想测试一下这个列表是否与我期望的列表相同。我编写了以下代码:
def expectedResults = [ ... ] //the list I expect to see
def isEqual = true;
when:
def realResults = getRealResultsMethod() //get real results in a list here
expectedResults.each {isEqual &= realResults.contains(it)}
then:
isEqual
0 * errorHandler.handleError(_) //by the way assert that my errorHandler is never called
这是我第一次使用 Groovy,所以我可能错过了一些东西吗?
PS
令我困惑的是 Groovy 和 Spock 中的“等于”运算符。给定 Java ArrayList 或 Java 数组,等于运算符只是恒等运算符:等于是 ==。据我了解,Groovy 中默认的 equals 运算符实际上是 equals(在这里形成:http://groovy.codehaus.org/Differences+from+Java)。但是 Groovy List 或 Set 的“等于”是什么?
更新
更准确地说。 我想知道两个列表是否具有相同的对象,两个列表没有额外的对象,顺序并不重要。例如:
list=[1,5,8]
list1=[5,1,8]
list2=[1,5,8,9]
println(list == list1) //should be equal, if we use == not equal
println(list == list2) //should not be equal, if we use == not equal
就这样做:
when:
def expectedResults = [ ... ]
def realResults = getRealResultsMethod()
then:
realResults == expectedResults
或者,如果你不关心顺序(这违反了 List 的契约,但你就这样了),你可以这样做:
then:
realResults.sort() == expectedResults.sort()
或者将它们转换为集合或其他东西
如果您只需要检查两个列表是否具有相同的元素,您可以尝试:
when:
def expectedResults = [ ... ]
def realResults = getRealResultsMethod()
then:
realResults.size() == expectedResults.size()
realResults.containsAll(expectedResults)
expectedResults.containsAll(realResults)
但是,如果您需要检查两个列表是否相等,您只需要(如 @tim_yates 的回复中所示):
when:
def expectedResults = [ ... ]
def realResults = getRealResultsMethod()
then:
realResults == expectedResults
请记住,仅当两个列表具有相同顺序的相同元素时,它们才相等。
您正在寻找的语义数据结构通常被称为bag。在包中,就像在集合中一样,元素的顺序并不重要。然而,在包中,就像在列表中一样,允许重复元素。那么,袋相等性包括具有相同数量的相同元素,尽管不一定具有相同的顺序。所以看来您正在寻找的是一种将“bag”语义应用于您的列表的方法。最简单的方法是复制其中一个袋子,然后从副本中删除另一个袋子的元素,直到:
类似于下面所示的
equals()
实现:
class Bag {
List list
Bag(List list) { this.list = list }
@Override boolean equals(that) {
def thisList = list?.clone() ?: []
that instanceof Bag &&
(that?.list ?: []).every { thisList.remove((Object)it) } &&
!thisList
}
@Override int hashCode() { this?.list?.sum { it?.hashCode() ?: 0 } ?: 0 }
@Override String toString() { this?.list?.toString() }
}
def a = [1, 5, 1, -1, 8] as Bag
def b = [5, 1, -1, 8, 1] as Bag // same elements different order
def c = [1, 5, -1, 8] as Bag // same elements different size
def d = [5, 5, 1, -1, 8] as Bag // same elements same size different amounts of each
assert a == b
assert a != c
assert a != d
println a // [1, 5, 1, -1, 8]
println b // [5, 1, -1, 8, 1]
或者,如果您根本不关心原始列表顺序,您可以将包表示为地图。 bag 元素值是映射键,每个 bag 元素出现的次数是映射值。到那时,平等就只是地图平等。
像这样:
class BagAsMap {
Map map = [:]
BagAsMap(List list) {
(list ?: []).each { map[it] = (map[it] ?: 0) + 1 }
}
@Override boolean equals(that) {
that instanceof BagAsMap && this?.map == that?.map
}
@Override int hashCode() { this?.map?.hashCode() ?: 0 }
@Override String toString() {
'[' + map.keySet().sum { k -> (0..<(map[k])).sum { "${k}, " } }[0..-3] + ']'
}
}
def a1 = [1, 5, 1, -1, 8] as BagAsMap
def b1 = [5, 1, -1, 8, 1] as BagAsMap // same elements different order
def c1 = [1, 5, -1, 8] as BagAsMap // same elements different size
def d1 = [5, 5, 1, -1, 8] as BagAsMap // same elements same size different amounts
assert a1 == b1
assert a1 != c1
assert a1 != d1
println a1
println b1
无论如何,如果您只需要检查顺序中立列表等价性一次或两次,那么这是严重的矫枉过正,但如果经常需要 Bag 语义,那么以这两种方式之一定义 Bag 类可能是一个好主意。
如其他地方所述,在这种特定情况下,
a.sort() == b.sort()
是代替完整包语义的充分权宜之计。但是,即使使用最复杂的比较器闭包,也并非所有可能一起放置在列表中的对象都可以相互排序。然而,它们都有 hashCode()
和 equals()
,这就是所示的任一包实现所需的全部内容。
此外,
List.sort()
具有 O(n log n) 算法复杂度,而显示的所有包操作都是 O(n) 复杂度。对于这些小列表不值得担心,但对于大列表则更重要。
从 Spock 2.1 开始,内置了一个比其他答案中提到的更好的选项:
=~
和 ==~
运算符。查看更多文档。
==~
完全符合您的要求:仅当两个列表包含相同数量的相同对象时才相等,忽略顺序。
您的情况:
def list = [1,5,8]
def list1 = [5,1,8]
def list2 = [1,5,8,9]
list ==~ list1 // true
list ==~ list2 // false
list1.containsAll(list2) && list2.containsAll(list1)