scala 单元测试
使您的单元测试更上一层楼。 (Taking your unit tests to the next level.)
I have been using ScalaCheck testing library for at least 2 years now. It allows you to take your unit tests to the next level.
我已经使用ScalaCheck测试库至少两年了。 它使您可以将单元测试提高到一个新的水平。
You can do
你可以做
Property-based testing by generating a lot of tests with random data and asserting properties on your functions. A simple code example is described below.
通过基于随机数据生成大量测试并在函数中声明属性来进行基于属性的测试 。 下面描述一个简单的代码示例。
You can do Law testing that is even more powerful and allows you to check mathematical properties on your types.
您可以进行功能更强大的Law测试 ,并可以检查类型的数学属性。
基于属性的测试 (Property-based testing)
Here is our beloved User
data type:
这是我们钟爱的User
数据类型:
case class User(name: String, age: Int)
And a random User
generator:
以及一个随机的User
生成器:
import org.scalacheck.{ Gen, Arbitrary }
import Arbitrary.arbitrary
implicit val randomUser: Arbitrary[User] = Arbitrary(for {
randomName <- Gen.alphaStr
randomAge <- Gen.choose(0,80)
} yield User(randomName, randomAge))
We can now generate a User
like this:
我们现在可以生成一个这样的User
:
scala> randomUser.arbitrary.sample
res0: Option[User] = Some(User(OtwlaaxGbmdhuorlmgvXitbmGfbgetm,22))
Let’s define some functions on the User
:
让我们在User
上定义一些功能:
def isAdult: User => Boolean = _.age >= 18
def isAllowedToDrink : User => Boolean = _.age >= 21
Let’s claim that:
让我们声称:
All adults are allowed to drink.
允许所有成年人喝酒。
Can we somehow prove this? Is this correct for all users?
我们能以某种方式证明这一点吗? 这对所有用户都正确吗?
This is where property testing comes to the rescue. It allows us not to write specific unit-tests. Here they would be:
这是进行性能测试的地方。 它使我们不必编写特定的单元测试。 他们将是:
- 18-year-olds are not allowed to drink 禁止18岁以下的人喝酒
- 19-year-olds are not allowed to drink 19岁以下的人不允许喝酒
- 20-year-olds are not allowed to drink 不允许20岁以下的人喝酒
All of these statements can be replaced by a single property check:
所有这些语句都可以由单个属性检查代替:
import org.scalacheck.Prop.forAll
val allAdultsCanDrink = forAll { u: User =>
if(isAdult(u)) isAllowedToDrink(u) else true }
Let’s run it:
让我们运行它:
scala> allAdultsCanDrink.check()
! Falsified after 0 passed tests.
> ARG_0: User(,19)
It fails as expected for a 19-year-old.
失败的原因是19岁。
Property testing is awesome for a few reasons:
性能测试非常棒,原因如下:
- Saves time by writing less specific tests 通过编写较少特定的测试来节省时间
- Finds new use cases generated by Scala check that you forgot to handle 查找您忘记处理的Scala检查生成的新用例
- Forces you think in a more general way 迫使您以更一般的方式思考
- Gives you more confidence for refactoring than conventional unit tests 比常规单元测试给您更多的重构信心
法律测试 (Law testing)
It gets better: let’s take it to the next level and define an Ordering between Users:
它会变得更好:让我们进入下一个级别,并定义用户之间的订购:
import scala.math.Orderingimplicit val userOrdering: Ordering[User] = Ordering.by(_.age)
We want to make sure that we didn't forget any edge cases and that we defined our order properly. This property has a name, and it’s called a total order. It needs to holds for the following properties:
我们要确保我们不会忘记任何边缘情况 ,并且我们正确定义了订单。 此属性有一个名称,称为总订单。 它需要保留以下属性:
Totality
合计
Antisymmetry
反对称
Transitivity
传递性
Can we somehow prove this? Is this correct for all users?
我们能以某种方式证明这一点吗? 这对所有用户都正确吗?
This is possible without writing a single test!
无需编写单个测试就可以!
We use cats-laws
library to define the laws we want to test on the ordering we defined:
我们使用cats-laws
库来定义要根据定义的顺序测试的法律:
import cats.kernel.laws.discipline.OrderTests
import cats._
import org.scalatest.FunSuite
import org.typelevel.discipline.scalatest.Discipline
import org.scalacheck.ScalacheckShapeless._
class UserOrderSpec extends FunSuite with Discipline {
//needed boilerplate to satisfy the dependencies of the framework
implicit def eqUser[A: Eq]: Eq[Option[User]] = Eq.fromUniversalEquals
//convert our standard ordering to a `cats` order
implicit val catsUserOrder: Order[User] = Order.fromOrdering(userOrdering)
//check all mathematical properties on our ordering
checkAll("User", OrderTests[User].order)
}
Let’s run it:
让我们运行它:
scala> new UserOrderSpec().execute()
UserOrderSpec:
- User.order.antisymmetry *** FAILED ***
GeneratorDrivenPropertyCheckFailedException was thrown during property evaluation.
(Discipline.scala:14)
Falsified after 1 successful property evaluations.
Location: (Discipline.scala:14)
Occurred when passed generated values (
arg0 = User(h,17),
arg1 = User(edsb,17),
arg2 = org.scalacheck.GenArities$$Lambda$2739/1277317528@41d7b4cf
)
Label of failing property:
Expected: true
Received: false
- User.order.compare
- User.order.gt
- User.order.gteqv
- User.order.lt
- User.order.max
- User.order.min
- User.order.partialCompare
- User.order.pmax
- User.order.pmin
- User.order.reflexitivity
- User.order.reflexitivity gt
- User.order.reflexitivity lt
- User.order.symmetry
- User.order.totality
- User.order.transitivity
Sure enough, it fails on the antisymmetry law! Same age and different names are not supposed to be equals. We forgot to use the name in our original Ordering
, so let's fix it and rerun the laws:
果然,它不符合反对称定律! 相同的年龄和不同的名字不应该相等。 我们忘记了在原始Ordering
使用该名称,因此让我们对其进行修复并重新执行法律:
implicit val userOrdering: Ordering[User] = Ordering.by( u => (u.age, u.name))
scala> new UserOrderSpec().execute()
UserOrderSpec:
- User.order.antisymmetry
- User.order.compare
- User.order.gt
- User.order.gteqv
- User.order.lt
- User.order.max
- User.order.min
- User.order.partialCompare
- User.order.pmax
- User.order.pmin
- User.order.reflexitivity
- User.order.reflexitivity gt
- User.order.reflexitivity lt
- User.order.symmetry
- User.order.totality
- User.order.transitivity
And now it passes :)
现在它通过了:)
If you are wondering what can you test besides Order
s, go check out the docs here: https://typelevel.org/cats/typeclasses/lawtesting.html
如果您想知道除Order
之外还可以测试什么,请在此处查看文档: https : //typelevel.org/cats/typeclasses/lawtesting.html
摘要 (Summary)
- Property tests are more powerful than unit tests. They allow us to define properties on functions and generate a large number of tests using random data generators. 属性测试比单元测试更强大。 它们使我们能够定义函数的属性,并使用随机数据生成器生成大量测试。
Law testing takes it to the next level and uses the mathematical properties of structures like
Order
to generate the properties and the tests.法律测试将其带入一个新的水平,并使用诸如
Order
之类的结构的数学特性来生成特性和测试。- Next time you define an ordering and wonder if it’s well-defined, go ahead and run the laws on it! 下次您定义一个命令并想知道它是否定义明确时,请继续执行该规则!
scala 单元测试