如何优化Scalatest中case类集合对比的错误输出格式?
问题:Scalatest中Case类集合对比的错误输出优化
当使用Scalatest测试框架对比case类集合时,默认错误输出可读性极差——所有元素挤在一行,大型case类的输出更是混乱,必须手动格式化才能定位差异。
示例代码
case class Test(a: String, b: String, c: String) "lists" should "match" in { val l1 = List( Test("a", "b", "c"), Test("d", "e", "f"), Test("g", "h", "i") ) val l2 = List( Test("a", "b", "c"), Test("d", "E", "f"), Test("g", "h", "i") ) l1 should contain theSameElementsAs l2 }
默认错误输出
lists - should match *** FAILED *** List(Test("a", "b", "c"), Test("d", "e", "f"), Test("g", "h", "i")) did not contain the same elements as List(Test("a", "b", "c"), Test("d", "E", "f"), Test("g", "h", "i")) (Test.scala:52)
期望输出格式
lists - should match *** FAILED *** List( Test("a", "b", "c"), Test("d", "e", "f"), Test("g", "h", "i") ) did not contain the same elements as List( Test("a", "b", "c"), Test("d", "E", "f"), Test("g", "h", "i") ) (Test.scala:52)
理想情况下还能直接标注出差异字段。
解决方案(无需更换测试框架)
完全可以通过Scalatest的自定义匹配器、内置格式化工具或Diffing功能实现需求,以下是具体方案:
1. 自定义匹配器(推荐:实现格式化+差异标注)
手动实现匹配器,重写错误消息的生成逻辑,对集合进行换行格式化,并直接标注差异元素:
import org.scalatest.matchers.{MatchResult, Matcher} import org.scalatest.wordspec.AnyWordSpec case class Test(a: String, b: String, c: String) class FormattedListMatchSpec extends AnyWordSpec { // 自定义匹配器:格式化输出集合+标注差异 def containTheSameElementsAsFormatted(expected: List[Test]): Matcher[List[Test]] = new Matcher[List[Test]] { override def apply(actual: List[Test]): MatchResult = { val actualSet = actual.toSet val expectedSet = expected.toSet val missingElements = expectedSet -- actualSet val extraElements = actualSet -- expectedSet // 格式化集合输出 def formatList(list: List[Test]): String = list.map(_.toString).mkString("List(\n ", ",\n ", "\n)") val formattedActual = formatList(actual) val formattedExpected = formatList(expected) // 构建错误消息 val failureMsg = if (missingElements.nonEmpty || extraElements.nonEmpty) { s"""$formattedActual |did not contain the same elements as |$formattedExpected | |差异详情: |- 缺失元素:${missingElements.mkString(", ")} |- 多余元素:${extraElements.mkString(", ")}""".stripMargin } else { s"$formattedActual did contain the same elements as $formattedExpected" } MatchResult( actualSet == expectedSet, failureMsg, failureMsg.replace("did not contain", "did contain") ) } } "lists" should "match with formatted output" in { val l1 = List( Test("a", "b", "c"), Test("d", "e", "f"), Test("g", "h", "i") ) val l2 = List( Test("a", "b", "c"), Test("d", "E", "f"), Test("g", "h", "i") ) l1 should containTheSameElementsAsFormatted(l2) } }
2. 利用Scalatest内置的PrettyPrinting
混入PrettyPrinting特质,使用pretty方法自动格式化对象输出,简化自定义匹配器的格式化逻辑:
import org.scalatest.PrettyPrinting import org.scalatest.matchers.{MatchResult, Matcher} import org.scalatest.wordspec.AnyWordSpec case class Test(a: String, b: String, c: String) class PrettyPrintMatchSpec extends AnyWordSpec with PrettyPrinting { def containTheSameElementsAsPretty(expected: List[Test]): Matcher[List[Test]] = new Matcher[List[Test]] { override def apply(actual: List[Test]): MatchResult = { val actualSet = actual.toSet val expectedSet = expected.toSet val isMatch = actualSet == expectedSet val failureMsg = s"""${pretty(actual)} |did not contain the same elements as |${pretty(expected)}""".stripMargin MatchResult(isMatch, failureMsg, failureMsg.replace("did not contain", "did contain")) } } // 测试用例同上... }
3. 使用Scalatest的Diffing功能生成结构化差异
Scalatest的Diffing模块可以生成更精细的字段级差异对比,适合复杂case类:
import org.scalatest.diff.Diffing import org.scalatest.matchers.{MatchResult, Matcher} import org.scalatest.wordspec.AnyWordSpec case class Test(a: String, b: String, c: String) class DiffingMatchSpec extends AnyWordSpec { def containTheSameElementsAsWithDiff(expected: List[Test]): Matcher[List[Test]] = new Matcher[List[Test]] { override def apply(actual: List[Test]): MatchResult = { val actualSet = actual.toSet val expectedSet = expected.toSet val isMatch = actualSet == expectedSet val diffMsg = if (!isMatch) { val diff = Diffing.diff(actual.toSeq.sorted, expected.toSeq.sorted) s"""集合不匹配: |实际值:${actual.mkString("[", ", ", "]")} |期望值:${expected.mkString("[", ", ", "]")} |差异: |${diff}""".stripMargin } else { "集合元素完全匹配" } MatchResult(isMatch, diffMsg, diffMsg) } } // 测试用例同上... }
内容的提问来源于stack exchange,提问作者fungtional
相关产品推荐
相关产品推荐

