比较列表时 "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。这与其他不错的功能一起,自动为您实现 hashcodeequalstoString 方法。

所以现在,我们可以有这样的东西:

// ...

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 中是这样的。