首页 > 解决方案 > 如何对 Numeric 的不同类型类实例进行单元测试?

问题描述

假设我有两个 typeclass 实例Numeric

class Money(c: String, x: Long, y: Int)
class Quantity(c: String, x: Long, y: Int)
implicit val numericMoney: Numeric[Money] = new Numeric[Money]
implicit val numericQuantity: Numeric[Quantity] = new Numeric[Quantity]

Money 和 Quantity 在 Numeric 实例中的行为应该相同。我有 scalaTest 测试来检查 Money 的行为是否正确。

例如

import implicits.NumericMoney.numericMoney._

class MoneyOpsSpec extends WordSpec with Matchers {

  val max = Money("", Long.MaxValue, 999999999)
  val min = Money("", Long.MinValue, -999999999)

  "A Money object" when {
    "zero" should {
      "be neutral element under addition" in {
        zero + Money("", 15, 50) should ===(Money("", 15, 50))
        Money("", 15, 50) + zero should ===(Money("", 15, 50))
      }
      "be neutral element under subtraction" in {
        zero - Money("", 15, 50) should ===(Money("", -15, -50))
        Money("", 15, 50) - zero should ===(Money("", 15, 50))
      }
      "be invariant under negation" in {
        -zero should ===(zero)
      }
    }
  }
}

Quantityspec 应该以同样的方式执行。我可以实现一个通用规范并使用MoneyQuantity作为该规范的输入吗?或者 scalaTest 或 specs2 是否有一些东西可以确保 Numeric 类型类实例的行为正确?我可以轻松切换测试框架。

标签: scalatypeclassscalatestspecs2

解决方案


我可以实现一个通用规范并使用 Money 和 Quantity 作为该规范的输入吗?

当然。只需将隐式作为构造函数参数即可。未经测试,但应该是近似的(变化很小):

abstract class NumOpsSpec[T](implicit num: Numeric[T], tag: ClassTag[T]) extends WordSpec with Matchers {
  import num._

  val max: T
  val min: T
  val someElement: T

  s"A ${tag.runtimeClass.simpleName} object" when {
    "zero" should {
      "be neutral element under addition" in {
        zero + someElement should ===(someElement)
        someElement + zero should ===(someElement)
      }
      "be neutral element under subtraction" in {
        zero - someElement should ===(- someElement)
        someElement - zero should ===(someElement)
      }
      "be invariant under negation" in {
        -zero should ===(zero)
      }
    }
  }
}

class MoneyOpsSpec extends NumOpsSpec[Money] {
  override val max = Money("", Long.MaxValue, 999999999)
  override val min = Money("", Long.MinValue, -999999999)
  override val someElement = Money("", 15, 50)
}

class QuantityOpsSpec extends NumOpsSpec[Quantity] {
  override val max = ???
  override val min = ???
  override val someElement = ???
}

您还可以查看https://github.com/typelevel/discipline以测试一般类型的法律。


推荐阅读