我有一个关于将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
我无法理解这里有什么区别。 我认为“应该是”匹配器比“===”更严格,但我不明白为什么。 任何人都可以向我解释一下吗?
注意:我想指出MyObject具有不同基于java的类型的各种属性,包括float。可能是这个原因?
更新 我用简单的类检查了matcher(参见下面的代码片段),如果我自己覆盖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"
}
}
}
UPDATE2 似乎“===”非常宽松地比较:我尝试了其他实例,其中一个对象的内容是不同的(即不同的“pippo”字符串),它告诉我它们是相同的。 所以它似乎比较了类的类型,而不是用反射做一些魔术...... 我对此越来越感到困惑,并开始认为我应该实现我的equals方法而忘记这个......
答案 0 :(得分:2)
关于===
我认为===
并不完全符合您的想法。您应该使用assert(1 === 4)
。正如scalatest doc中指出的那样:
ScalaTest允许您使用Scala的断言语法,但定义了三元组 equals运算符(===)为您提供更好的错误消息。下列 代码会给你一个错误,只显示一个断言失败:
assert(1 == 2)
使用三等于代替会给你更多 信息性错误消息," 1不等于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
所以是的,你必须......
重新实现等于和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可以为您做更多的事情!
案例类
在类定义之前使用关键字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)
// ...
如果你真的想深入了解并希望更深入地了解平等解决方案和陷阱,我建议马丁的Odersky的文章How to Write an Equality Method in Java来理解Java中的一些平等问题。这有助于理解为什么有些东西就像它们在Scala中一样。