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での私の最初の経験の1つなので、何かが足りないのではないでしょうか。
PS
私を混乱させるのは、GroovyとSpockの「equals」演算子です。与えられたJava ArrayListまたはJava array、equals演算子は単に恒等演算子です:equalsは==です。私が理解している限り、Groovyではデフォルトのequals演算子は実際に等しいです(ここのフォーム: http://groovy.codehaus.org/Differences+from+Java )しかし、Groovyリストまたはセットの「等しい」とは何ですか?
更新
より正確には。 2つのリストに同じオブジェクトがあり、両方のリストに追加のオブジェクトがないかどうかを確認したいのですが、順序は関係ありません。例えば:
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
または、順序を気にしない場合(リストの契約を破っていますが、そこに行きます)、次のことができます。
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
2つのリストは、同じ要素が同じorderである場合にのみ等しいことに注意してください。
探しているセマンティックデータ構造は、しばしばbagと呼ばれます。バッグの中では、セットのように、要素の順序は重要ではありません。ただし、バッグ内では、リストのように、要素を繰り返すことができます。したがって、バッグの平等は、必ずしも同じ順序である必要はありませんが、同じ要素をそれぞれ同じ量で持つことで構成されます。したがって、探しているのは、リストに「バッグ」セマンティクスを適用する方法のようです。これを行う最も簡単な方法は、バッグの1つを複製し、次の状態になるまで複製から他のバッグの要素を削除することです。
以下に示す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
_
いずれにせよ、順序に依存しないリストの同等性を1回または2回チェックする必要がある場合、これは深刻なやり過ぎですが、バッグのセマンティクスが頻繁に必要になる場合は、これら2つの方法のいずれかでBagクラスを定義することをお勧めします。
他の場所で述べたように、この特定のケースでは、a.sort() == b.sort()
はフルバッグセマンティクスの代わりに十分な一時的なギャップです。ただし、最も洗練されたコンパレータクロージャを使用しても、リストに一緒に配置される可能性のあるすべてのオブジェクトが相互にソートできるわけではありません。ただし、それらはすべてhashCode()
とequals()
を持っています。これは、示されているいずれかのバッグの実装に必要なすべてです。
さらに、List.sort()
にはO(n log n)アルゴリズムの複雑さがありますが、示されているすべてのバッグ操作はO(n)複雑です。これらの小さなリストについては心配する価値はありませんが、大きなリストについてははるかに大きな取引です。