ScalaTest - 编写自定义匹配器

ScalaTest - 编写自定义匹配器

问题描述:

我在为 NodeSeq 编写自定义匹配器时遇到问题:

I am running into a problem while writing a custom matcher for NodeSeq:

private def matchXML(expected: NodeSeq) = new Matcher[NodeSeq] {
  def apply(left: NodeSeq): MatchResult = MatchResult(left xml_== expected,
    "XML structure was not the same (watch spaces in tag texts)",
    "XML messages were equal")
}

这可以编译,但代码如下:

This compiles, but the following piece of code:

val expected : NodeSeq = ...
val xml : NodeSeq = ... 
xml should matchXML(expected)

原因:

error: overloaded method value should with alternatives:
(beWord: XMLStripDecoratorTests.this.BeWord)XMLStripDecoratorTests.this.ResultOfBeWordForAnyRef[scala.collection.GenSeq[scala.xml.Node]] <and>
(notWord: XMLStripDecoratorTests.this.NotWord)XMLStripDecoratorTests.this.ResultOfNotWordForAnyRef[scala.collection.GenSeq[scala.xml.Node]] <and>
(haveWord: XMLStripDecoratorTests.this.HaveWord)XMLStripDecoratorTests.this.ResultOfHaveWordForSeq[scala.xml.Node] <and>
(rightMatcher: org.scalatest.matchers.Matcher[scala.collection.GenSeq[scala.xml.Node]])Unit
cannot be applied to (org.scalatest.matchers.Matcher[scala.xml.NodeSeq])
xml should (matchXML(expected))

知道这意味着什么吗?

为什么无法进行类型检查:

类型检查器的工作方式如下.

The type checker works in the following way.

xml.should(matchXML(expected))

  • 因为方法 should 不是 NodeSeq 的一部分,编译器试图找到一个 隐式转换 xmlShouldMatcher.Programming in Scala"一书指定这种隐式转换应该是最具体的:
    • Because the method should is not part of a NodeSeq, the compiler tries to find an implicit conversion for xml to a ShouldMatcher. The book "Programming in Scala" specifies that such implicit conversion should be the most specific:
    • 直到 Scala 2.7,这就是故事的结局.无论何时应用了多个隐式转换,编译器拒绝选择它们之间.... Scala 2.8 放宽了这条规则.如果可用的其中之一转换比其他转换更具体,那么编译器会选择更具体的一个.... 一种隐式转换如果以下情况之一适用,则比另一个更具体:前者的参数类型是后者的子类型..."

      "Up through Scala 2.7, that was the end of the story. Whenever multiple implicit conversions applied, the compiler refused to choose between them. ... Scala 2.8 loosens this rule. If one of the available conversions is strictly more specific than the others, then the compiler will choose the more specific one. ... one implicit conversion is more specific than another if one of the following applies: The argument type of the former is a subtype of the latter’s. .."

      • 因为NodeSeq扩展了Seq[Node],所以下面的函数

        • Because NodeSeq extends Seq[Node], the following function

          convertToSeqShouldWrapper[T](o : scala.GenSeq[T]) : SeqShouldWrapper[T]

          因此是最具体的一个.

          程序改写为:

          `convertToSeqShouldWrapper(xml).should(matchXML(expected))`
          

          其中 convertToSeqShouldWrapper(xml)SeqShouldWrapper[T] 其中 T = GenSeq[Node].

          where convertToSeqShouldWrapper(xml) is a SeqShouldWrapper[T] where T = GenSeq[Node].

          SeqShouldWrapper 中的方法 should 接受一个 Matcher[T],它是一个 T => 类型的函数.匹配结果.因此,它接受一个 Matcher[GenSeq[Node]].

          The method should from SeqShouldWrapper accepts a Matcher[T] which is a function of type T => MatchResult. Therefore, it accepts a Matcher[GenSeq[Node]].

          因为 T 出现在箭头的左边,匹配器不是 协变,但逆变.NodeSeqGenSeq[Node],所以 Matcher[GenSeq[Node]]Matcher[NodeSeq]code>,而不是相反.这解释了上述错误,其中方法 should 不能接受 Matcher[NodeSeq] 并且需要 Matcher[GenSeq[Node]].

          Because T is appearing to the left of the arrow, matchers are not covariant in T, but contravariant. A NodeSeq is a GenSeq[Node], so a Matcher[GenSeq[Node]] is a Matcher[NodeSeq], not the opposite.This explains the above error, where the method should cannot accept a Matcher[NodeSeq] and requires a Matcher[GenSeq[Node]].

          2 个解决方案

          • NodeSeq 的所有实例替换为 GenSeq[Node],以便类型在任何地方都匹配.
          • 或者,使用转换函数显式包装 xml.

          • Replace All instances of NodeSeq to GenSeq[Node] so that the type matches everywhere.
          • Alternatively, wrap xml explicitely with the conversion function.

          convertToAnyShouldWrapper(xml).should(matchXML(expected))