比较列表时 "should be" 和“===”之间的 Scala FunSuite 区别
Scala FunSuite difference between "should be" and "===" when comparing lists
我对将 FunSuite 与 Matchers 结合使用有疑问。
我正在尝试与对象列表进行比较,所以我尝试这样做:
orders2 should be(tiOrdersList2)
其中 orders2 和 tiOrdersList2 是 scala.List[MyObject]
这场比赛惨败,所以我试了一下
orders2.toSeq should be (tiOrdersList2.toSeq)
运气不好
然后我尝试匹配单个项目
orders2(0) should be (tiOrdersList2(0))
又没运气
最后我发现要使它适用于我必须使用“===”的单个对象并回到第一个案例我设法让它工作:
orders2 === tiOrdersList2
我不明白这里有什么区别。
我想 "should be" 匹配器比 "===" 更严格,但我不明白为什么。
谁能给我解释一下?
注意:我想指出 MyObject 具有基于不同 java 类型的各种属性,包括 float。可能是这个原因吗?
更新
我用简单的 classes 检查了匹配器(见下面的代码片段),如果我自己重写 equals 方法,"should be" 也有效,所以主要问题是“==”是什么样的比较="干什么?
import org.scalatest.{Matchers, FunSuite}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class blahSuite extends FunSuite with Matchers
{
test("extractSessions") {
new Object1 === (new Object1)
new Object2 === (new Object2)
new Object1 shouldEqual (new Object1)
new Object2 should be (new Object2)
}
class Object1 {
val pippo: java.lang.String = "blah"
val pappo:java.lang.Long = 0l
override def toString(): String = {
s"$pippo, $pappo"
}
}
class Object2 {
val pippo: java.lang.String = "blah"
val pappo:java.lang.Long = 0l
val pluto:java.lang.Float = 0.0f
override def toString(): String = {
s"$pippo, $pappo, $pluto"
}
}
}
更新2
似乎“===”比较松散:我尝试了对象内容不同的其他实例(即不同的 "pippo" 字符串),它告诉我它们是相同的。
所以它似乎比较 class 类型而不是用反射做一些魔术......
我对此越来越困惑,并开始认为我应该实现我的 equals 方法并忘记它......
关于===
我认为 ===
与您的想法不完全相同。你应该使用 assert(1 === 4)
。正如 scalatest doc 中指出的:
ScalaTest lets you use Scala's assertion syntax, but defines a triple
equals operator (===) to give you better error messages. The following
code would give you an error indicating only that an assertion failed:
assert(1 == 2)
Using triple equals instead would give you the more
informative error message, "1 did not equal 2":
assert(1 === 2)
因此 ===
与 ==
相同。
(请注意,您也可以在 docs 中提到的 result should === (3)
等匹配器中使用 ===
,但我认为这对您来说现在并不重要,只是为了让你知道它们是不同的野兽)
应该是(工作?)
Should be
也不起作用。问题是第一个失败的断言 (shouldEqual
) 会抛出错误并停止执行,所以我们从来没有 运行 should be
。如果更改顺序,您还会看到 should be
的错误:
new Object2 should be (new Object2)
new Object1 shouldEqual (new Object1)
[info] - extractSessions *** FAILED ***
[info] blah, 0, 0.0 was not equal to blah, 0, 0.0 (testa.scala:22)
比较
正如您已经发现的,我们不能只比较对象:
val a = new Object1
val b = new Object1
println(a == b) // false
// Or the equivalent version
println(a.equals(b)) // false
粗略地说,当您调用 ==
时,您实际上是在调用这样的东西:
println(a.hashCode == b.hashCode) // false
而且它们显然不一样,因为hashCode的默认实现是基于当前对象的内存地址:
println(a.hashCode) // 769724695
println(b.hashCode) // 757278160
所以是的,你必须...
重新实现equals和hashCode
就像在这个 Scala Cookbook recipe 中所做的那样,您可以这样做:
test("extractSessions") {
new Object3 should be (new Object3)
new Object3 shouldEqual (new Object3)
}
class Object3 {
// PS: A little code improvement:
// I removed the old ugly, java like
// val pippo: java.lang.String = "blah"
// with the more scala-like:
val pippo = "blah" // Scala guesses the type -> String
val pappo = 0L // again -> Long
def canEqual(a: Any) = a.isInstanceOf[Object3]
override def equals(that: Any): Boolean =
that match {
case that: Object3 => that.canEqual(this) && this.hashCode == that.hashCode
case _ => false
}
override def hashCode: Int = (41 * (41 + pippo.hashCode) + pappo.hashCode)
}
所以...我总是必须为我的对象重新实现 hashCode 和 equals。所以是这个吗?就这些了吗?
没有!
Scala 可以为您做更多!
案例class
当您在 class 定义之前使用关键字 case
时,您会创建一个 case class。这与其他不错的功能一起,自动为您实现 hashcode
、equals
和 toString
方法。
所以现在,我们可以有这样的东西:
// ...
test("extractSessions") {
assert(Object1("blah", 0L) === Object1("blah", 0L))
assert(Object2("blah", 0L, 0.0F) === Object2("blah", 0L, 0.0F))
Object1("blah", 0L) shouldEqual (Object1("blah", 0L))
Object2("blah", 0L, 0.0F) should be (Object2("blah", 0L, 0.0F))
// Also works
new Object1("blah", 0L) shouldEqual (new Object1("blah", 0L))
}
case class Object1(pippo: String, pappo: Long)
case class Object2(pippo: String, pappo: Long, pluto: Float)
// ...
如果你真的想更深入,想更深入地理解等式解决方案和陷阱,我推荐Martin's Odersky的文章How to Write an Equality Method in Java来了解一点[=89=中的等式问题].这有助于理解为什么有些事情在 Scala 中是这样的。
我对将 FunSuite 与 Matchers 结合使用有疑问。
我正在尝试与对象列表进行比较,所以我尝试这样做:
orders2 should be(tiOrdersList2)
其中 orders2 和 tiOrdersList2 是 scala.List[MyObject]
这场比赛惨败,所以我试了一下
orders2.toSeq should be (tiOrdersList2.toSeq)
运气不好
然后我尝试匹配单个项目
orders2(0) should be (tiOrdersList2(0))
又没运气
最后我发现要使它适用于我必须使用“===”的单个对象并回到第一个案例我设法让它工作:
orders2 === tiOrdersList2
我不明白这里有什么区别。 我想 "should be" 匹配器比 "===" 更严格,但我不明白为什么。 谁能给我解释一下?
注意:我想指出 MyObject 具有基于不同 java 类型的各种属性,包括 float。可能是这个原因吗?
更新 我用简单的 classes 检查了匹配器(见下面的代码片段),如果我自己重写 equals 方法,"should be" 也有效,所以主要问题是“==”是什么样的比较="干什么?
import org.scalatest.{Matchers, FunSuite}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class blahSuite extends FunSuite with Matchers
{
test("extractSessions") {
new Object1 === (new Object1)
new Object2 === (new Object2)
new Object1 shouldEqual (new Object1)
new Object2 should be (new Object2)
}
class Object1 {
val pippo: java.lang.String = "blah"
val pappo:java.lang.Long = 0l
override def toString(): String = {
s"$pippo, $pappo"
}
}
class Object2 {
val pippo: java.lang.String = "blah"
val pappo:java.lang.Long = 0l
val pluto:java.lang.Float = 0.0f
override def toString(): String = {
s"$pippo, $pappo, $pluto"
}
}
}
更新2 似乎“===”比较松散:我尝试了对象内容不同的其他实例(即不同的 "pippo" 字符串),它告诉我它们是相同的。 所以它似乎比较 class 类型而不是用反射做一些魔术...... 我对此越来越困惑,并开始认为我应该实现我的 equals 方法并忘记它......
关于===
我认为 ===
与您的想法不完全相同。你应该使用 assert(1 === 4)
。正如 scalatest doc 中指出的:
ScalaTest lets you use Scala's assertion syntax, but defines a triple equals operator (===) to give you better error messages. The following code would give you an error indicating only that an assertion failed:
assert(1 == 2)
Using triple equals instead would give you the more informative error message, "1 did not equal 2":
assert(1 === 2)
因此 ===
与 ==
相同。
(请注意,您也可以在 docs 中提到的 result should === (3)
等匹配器中使用 ===
,但我认为这对您来说现在并不重要,只是为了让你知道它们是不同的野兽)
应该是(工作?)
Should be
也不起作用。问题是第一个失败的断言 (shouldEqual
) 会抛出错误并停止执行,所以我们从来没有 运行 should be
。如果更改顺序,您还会看到 should be
的错误:
new Object2 should be (new Object2)
new Object1 shouldEqual (new Object1)
[info] - extractSessions *** FAILED ***
[info] blah, 0, 0.0 was not equal to blah, 0, 0.0 (testa.scala:22)
比较
正如您已经发现的,我们不能只比较对象:
val a = new Object1
val b = new Object1
println(a == b) // false
// Or the equivalent version
println(a.equals(b)) // false
粗略地说,当您调用 ==
时,您实际上是在调用这样的东西:
println(a.hashCode == b.hashCode) // false
而且它们显然不一样,因为hashCode的默认实现是基于当前对象的内存地址:
println(a.hashCode) // 769724695
println(b.hashCode) // 757278160
所以是的,你必须...
重新实现equals和hashCode
就像在这个 Scala Cookbook recipe 中所做的那样,您可以这样做:
test("extractSessions") {
new Object3 should be (new Object3)
new Object3 shouldEqual (new Object3)
}
class Object3 {
// PS: A little code improvement:
// I removed the old ugly, java like
// val pippo: java.lang.String = "blah"
// with the more scala-like:
val pippo = "blah" // Scala guesses the type -> String
val pappo = 0L // again -> Long
def canEqual(a: Any) = a.isInstanceOf[Object3]
override def equals(that: Any): Boolean =
that match {
case that: Object3 => that.canEqual(this) && this.hashCode == that.hashCode
case _ => false
}
override def hashCode: Int = (41 * (41 + pippo.hashCode) + pappo.hashCode)
}
所以...我总是必须为我的对象重新实现 hashCode 和 equals。所以是这个吗?就这些了吗?
没有!
Scala 可以为您做更多!
案例class
当您在 class 定义之前使用关键字 case
时,您会创建一个 case class。这与其他不错的功能一起,自动为您实现 hashcode
、equals
和 toString
方法。
所以现在,我们可以有这样的东西:
// ...
test("extractSessions") {
assert(Object1("blah", 0L) === Object1("blah", 0L))
assert(Object2("blah", 0L, 0.0F) === Object2("blah", 0L, 0.0F))
Object1("blah", 0L) shouldEqual (Object1("blah", 0L))
Object2("blah", 0L, 0.0F) should be (Object2("blah", 0L, 0.0F))
// Also works
new Object1("blah", 0L) shouldEqual (new Object1("blah", 0L))
}
case class Object1(pippo: String, pappo: Long)
case class Object2(pippo: String, pappo: Long, pluto: Float)
// ...
如果你真的想更深入,想更深入地理解等式解决方案和陷阱,我推荐Martin's Odersky的文章How to Write an Equality Method in Java来了解一点[=89=中的等式问题].这有助于理解为什么有些事情在 Scala 中是这样的。