scala 单元测试_Scala中的法律测试简介

scala 单元测试

Property-based law testing is one of the most powerful tools in the scala ecosystem. In this post, I’ll explain how to use law testing and the value it’ll give you using in-depth code examples.

基于财产的法律测试是scala生态系统中最强大的工具之一。 在这篇文章中,我将通过深入的代码示例来说明如何使用法律测试,以及它将为您带来的价值。

This post is aimed for Scala developers who want to improve their testing knowledge and skills. It assumes some knowledge of Scala, cats, and other functional libraries.

这篇文章面向想要提高测试知识和技能的Scala开发人员。 它假定您对Scala,猫和其他功能库有所了解。

介绍 (Introduction)

  • You might be familiar with types which are a set of values (for example Int values are : 1,2,3… String values are : “John Doe” etc).

    您可能熟悉作为一组值的类型(例如,Int值是: 1,2,3 …字符串值是: “John Doe”等)。

  • You also might be familiar with functions which is a mapping from Input type to Output type.

    您可能还熟悉从输入类型到输出类型的映射的函数。
  • A property is defined on a type or a function and describes its desired behavior.

    属性是在类型或函数上定义的,并描述其所需的行为。

So what is a Law? Keep on reading!

那么什么是法律? 继续阅读!

一个具体的例子 (A concrete example)

Here is our beloved Person data type:

这是我们心爱的Person数据类型:

case class Person(name: String, age: Int)

And serialization code using the Play-Json , a library that allows transforming your Person type into JSON :

以及使用Play-Json序列化的代码,该库允许将您的Person类型转换为JSON :

val personFormat: OFormat[Person] = new OFormat[Person] {override def reads(json: JsValue): JsResult[Person] = {val name = (json \ "name").as[String]val age = (json \ "age").as[Int]JsSuccess(Person(name, age))}
override def writes(o: Person): JsObject =JsObject(Seq("name" -> JsString(o.name), "age" -> JsNumber(o.age)))
}

We can now test this serialization function on a specific input like this:

现在,我们可以在特定的输入上测试此序列化功能,如下所示:

import org.scalatest._
class PersonSerdeSpec extends WordSpecLike with Matchers {"should serialize and deserialize a person" in {val person = Person("John Doe", 32)val actualPerson =personFormat.reads(personFormat.writes(person))actualPerson.asOpt.shouldEqual(Some(person))}
}

But, we now need to ask ourselves, whether all people will serialize successfully? What about a person with invalid data (such as negative age)? Will we want to repeat this thought process of finding edge-cases for all our test data?

但是,我们现在需要问自己,是否所有人都会成功进行序列化? 一个数据无效的人(例如负数年龄)怎么办? 我们是否要重复为所有测试数据寻找边缘情况的思考过程?

And most importantly, will this code remain readable over time? (e.g.: changing the person data type [adding a LastName field], repeated tests for other data types, etc)

而且最重要的是,随着时间的流逝,此代码是否仍然可读? (例如:更改person数据类型[添加LastName字段],对其他数据类型进行重复测试等)

“ We can solve any problem by introducing an extra level of indirection”.
“我们可以通过引入额外的间接级别来解决任何问题”。

基于属性的测试 (Property-based testing)

The first weapon in our disposal is Property-based testing (PBT). PBT works by defining a property, which is a high-level specification of behavior that should hold for all values of the specific type.

我们使用的第一个武器是基于属性的测试(PBT)。 PBT通过定义属性来工作,该属性是行为的高级规范,应适用于特定类型的所有值。

In our example, the property will be:

在我们的示例中,该属性为:

  • For every person p, if we serialize and deserialize them, we should get back the same person.

    对于每个人p,如果我们对它们进行序列化和反序列化,我们应该找回同一个人。

Writing this property using scala check looks like this:

使用scala check编写此属性如下所示:

object PersonSerdeSpec extends org.scalacheck.Properties("PersonCodec") {property("round trip consistency") = 
org.scalacheck.Prop.forAll { a: Person =>personFormat.reads(personFormat.writes(a)).asOpt.contains(a)}
}

The property check requires a way to generate Persons. This is done by using an Arbitrary[Person] which can be defined like this:

属性检查需要一种生成人员的方法。 这是通过使用Arbitrary[Person] ,可以这样定义:

implicit val personArb: Arbitrary[Person] = Arbitrary {for {name <- Gen.alphaStrage  <- Gen.chooseNum(0, 120)} yield Person(name, age)
}

Furthermore, we can use “scalacheck-shapeless”- an amazing library which eliminates (almost) all needs for the verbose (quite messy and highly bug-prone) arbitrary type definition by generating it for us!

此外,我们可以使用“scalacheck-shapeless” -一个令人惊叹的库,它通过为我们生成(几乎)消除了对冗长(相当混乱且容易出错的高度)任意类型定义的所有需求!

This can be done by adding:

可以通过添加以下内容来完成:

libraryDependencies += "com.github.alexarchambault" %% "scalacheck-shapeless_1.14" % "1.2.0"

and importing the following in our code:

并在我们的代码中导入以下内容:

import org.scalacheck.ScalacheckShapeless._

And then we can remove the personArb instance we defined earlier.

然后我们可以删除personArb 实例 我们之前定义的。

编解码法 (The Codec Law)

Let’s try to abstract further, by defining the laws of our data type:

让我们通过定义数据类型的规律来进一步抽象:

trait CodecLaws[A, B] {def serialize: A => Bdef deserialize: B => Adef codecRoundTrip(a: A): Boolean = serialize.
andThen(deserialize)(a) == a
}

This means That given

这意味着给定

  • The types A, B

    类型A, B

  • A function from A to B

    A to B功能

  • A function from B to A

    B to A功能

We define a function called “codecRoundTrip” which takes an “a: A” and takes it through the functions and makes sure we get the same value of type A back.

我们定义了一个名为“ codecRoundTrip ”的函数,该函数采用“a: A”并通过这些函数,并确保返回与A类型相同的值。

This Law states (without giving away any implementation details), that the roundtrip we do on the given input does not “lose” any information.

该法律规定(不提供任何实现细节 ),我们在给定输入上进行的往返操作不会“丢失”任何信息。

Another way of saying just that is by claiming that our types A and B are isomorphic.
换句话说,就是宣称我们的类型A和B是同构的。

We can abstract even more, by using the cats-laws library with the IsEq case-class for defining an Equality description.

通过使用带有IsEq案例类的cats-laws库来定义平等描述,我们可以进一步抽象。

import cats.laws._
trait CodecLaws[A, B] {def serialize: A => Bdef deserialize: B => Adef codecRoundTrip(a: A): cats.laws.IsEq[A] = serialize.andThen(deserialize)(a) <-> a
}
/** Represents two values of the same type that are expected to be equal. */
final case class IsEq[A](lhs: A, rhs: A)

What we get from this type and syntax is a description of equality between the two values instead of the equality result like before.

我们从这种类型和语法中得到的是两个值之间相等性的描述 而不是像以前一样的相等性结果。

编解码器测试 (The Codec Test)

It is time to test the laws we just defined. In order to do that, we will use the “discipline” library.

现在该测试我们刚刚定义的法律了。 为此,我们将使用“ 学科 ”库。

import cats.laws.discipline._
import org.scalacheck.{ Arbitrary, Prop }
trait CodecTests[A, B] extends org.typelevel.discipline.Laws {def laws: CodecLaws[A, B]def tests(implicitarbitrary: Arbitrary[A],eqA: cats.Eq[A]): RuleSet =new DefaultRuleSet(name   = name,parent = None,"roundTrip" -> Prop.forAll { a: A =>laws.codecRoundTrip(a)})
}

We define a CodecTest trait that takes 2 type parameters A and B, which in our example will be Person and JsResult.

我们定义一个CodecTest特征,它接受2个类型参数A and B ,在我们的示例中为PersonJsResult

The trait holds an instance of the laws and defines a test method that takes an Arbitrary[A] and an equality checker (of type Eq[A]) and returns a rule-set for scalacheck to run.

该特征包含一个法律实例,并定义一个测试方法,该方法采用Arbitrary[A]和相等性检查器(类型Eq[A] )并返回rule-set以供scalacheck运行。

Note that no tests actually run here. This gives us the power to run these tests which are defined just once for all the types we want

请注意,此处没有实际运行测试。 这使我们能够运行这些测试,这些测试针对我们想要的所有类型只定义了一次

We can now commit to a specific type and implementation (like Play-Json serialization) by instantiating a CodecTest with the proper types.

现在,我们可以通过实例化具有适当类型的CodecTest来承诺特定的类型和实现(例如Play-Json序列化)。

object JsonCodecTests {def apply[A: Arbitrary](implicit format: Format[A]): CodecTests[A, JsValue] =new CodecTests[A, JsValue] {override def laws: CodecLaws[A, JsValue] =CodecLaws[A, JsValue](format.reads, format.writes)}
}

(类型)绕行 (A (type) detour)

But now we get the error:

但是现在我们得到了错误:

Error:(11, 38) type mismatch;found   : play.api.libs.json.JsResult[A]required: A

We expected the types to flow from:

我们期望类型来自:

A  =>  B  =>  A

But Play-Json types go from:

但是Play-Json类型来自:

A  =>  JsValue  =>  JsResult[A]

This means that our deserialize function can succeed or fail and will not always return an A, but rather a container of A.

这意味着我们的反序列化功能可以成功或失败,并且不会总是返回A,而是返回A的容器。

In order to abstract over the types, we now need to use the F[_] type constructor syntax:

为了抽象类型,我们现在需要使用F[_]类型构造函数语法:

trait CodecLaws[F[_],A, B] {def serialize: A => Bdef deserialize: B => F[A]def codecRoundTrip(a: A)(implicit app:Applicative[F]): IsEq[F[A]] =serialize.andThen(deserialize)(a) <-> app.pure(a)
}

The Applicative instance is used to take a simple value of type A and lift it into the Applicative context which returns a value of type F[A].

Applicative实例用于获取类型A的简单值,并将其提升到Applicative上下文中,该上下文返回类型为F[A]

This process is similar to taking some value x and lifting it to an Option context using Some(x), or in our concrete example taking a value a:A and lifting it to the JsResult type using JsSuccess(a).

此过程类似于获取一些值x并使用Some(x)将其提升到Option上下文,或者在我们的具体示例中,获取值a:A并使用JsSuccess (a)将其提升为JsResult类型。

We can now finish the implementation for CodecTests and JsonCodecTests like this:

现在,我们可以像这样完成CodecTestsJsonCodecTests的实现:

trait CodecTests[F[_], A, B] extends org.typelevel.discipline.Laws {def laws: CodecLaws[F, A, B]def tests(implicitarbitrary: Arbitrary[A],eqA: cats.Eq[F[A]],applicative: Applicative[F]): RuleSet =new DefaultRuleSet(name   = name,parent = None,"roundTrip" -> Prop.forAll { a: A =>laws.codecRoundTrip(a)})
}
object JsonCodecTests {def apply[A: Arbitrary](implicit format: Format[A]): CodecTests[JsResult, A, JsValue] =new CodecTests[JsResult, A, JsValue] {override def laws: CodecLaws[JsResult, A, JsValue] =CodecLaws[JsResult, A, JsValue](format.reads, format.writes)}
}

And to define a working Person serialization test in 1 line of code:

并在1行代码中定义一个工作Person序列化测试

import JsonCodecSpec.Person
import play.api.libs.json._
import org.scalacheck.ScalacheckShapeless._
import org.scalatest.FunSuiteLike
import org.scalatest.prop.Checkers
import org.typelevel.discipline.scalatest.Discipline
class JsonCodecSpec extends Checkers with FunSuiteLike with Discipline { checkAll("PersonSerdeTests", JsonCodecTests[Person].tests)
}

抽象的力量 (The power of abstraction)

We were able to define our tests and laws without giving away any implementation details. This means we can switch to using a different library for serialization tomorrow and all our laws and tests will still hold.

我们能够定义测试和法律,而无需给出任何实施细节。 这意味着我们明天可以切换到使用其他库进行序列化,并且所有法律和测试仍然有效。

另一个例子 (Another example)

We can test this theory by adding support to BSON serialization using the reactive-mongo library:

我们可以通过使用react reactive-mongo库为BSON序列化添加支持来测试该理论:

import cats.Id
import io.bigpanda.laws.serde.{ CodecLaws, CodecTests }
import org.scalacheck.Arbitrary
import reactivemongo.bson.{ BSONDocument, BSONReader, BSONWriter }
object BsonCodecTests {def apply[A: Arbitrary](implicitreader: BSONReader[BSONDocument, A],writer: BSONWriter[A, BSONDocument]): CodecTests[Id, A, BSONDocument] =new CodecTests[Id, A, BSONDocument] {override def laws: CodecLaws[Id, A, BSONDocument] =CodecLaws[Id, A, BSONDocument](reader.read, writer.write)
override def name: String = "BSON serde tests"}
}

The types here flow from

这里的类型来自

A => BsonDocument => A

and not F[A] as we had expected. Luckily for us, we have a solution and use the Id-type to represent just that.

而不是我们所期望的F[A] 。 对我们来说幸运的是,我们有一个解决方案,并使用Id类型来表示。

And given the (very long) serializer definition:

并给出(很长)的序列化器定义:

implicit val personBsonFormat: BSONReader[BSONDocument, Person] with BSONWriter[Person, BSONDocument] =new BSONReader[BSONDocument, Person] with BSONWriter[Person, BSONDocument] {override def read(bson: BSONDocument): Person =Person(bson.getAs[String]("name").get, bson.getAs[Int]("age").get)
override def write(t: Person): BSONDocument =BSONDocument("name" -> t.name, "age" -> t.age)}

we can now define BsonCodecTests in all its 1 line of logic glory.

我们现在可以在所有1条逻辑荣耀中定义BsonCodecTests。

class BsonCodecSpec extends Checkers with FunSuiteLike with Discipline {checkAll("PersonSerdeTests", BsonCodecTests[Person].tests)
}

我们代码的(一阶)逻辑观点 (A (First-order) logic perspective on our code)

Our first test attempt can be described as follows:

我们的第一次测试尝试可以描述如下:

∃p:Person,s:OFormat that holds : s.read(s.write(p)) <-> p

Meaning, for the specific person p(“John Doe”,32) and for the format s, the following statement is true: decode(encode(p)) <-> p.

意思是, for the specific person p(“John Doe”,32)for the format s ,以下说法是正确的: decode(encode(p)) <- > p。

The second attempt (using PBT) can be:

第二次尝试(使用PBT )可以是:

∃s:OFormat, ∀p:Person the following should hold :  s.read(s.write(p)) <-> p

Which means, for all persons p and for the specific format s, the following is true: decode(encode(p))<->p.

这意味着, for all persons pfor the specific format s ,以下条件是正确的: decode(encode(p))< -> p。

The third (and most powerful statement thus far) using law testing:

使用law testing的第三个(也是迄今为止最有力的陈述):

∀s:Encoder, ∀p:Person the the following should hold :  s.read(s.write(p)) <-> p

Which means, for all formats s, and for all persons p, the following is true: decode(encode(p))<->p.

这意味着, for all formats sfor all persons p ,以下内容均成立: decode(encode(p))< -> p。

摘要 (Summary)

  • Law testing allows you to reason about your data-types and functions in a mathematical and concise way and provides a totally new paradigm for testing your code!

    法律测试使您能够以数学简洁的方式推理数据类型和函数,并为测试代码提供了全新的范例!
  • Most of the type level libraries you use (like cats, circe and many more) use law testing internally to test their data-types.

    您使用的大多数类型级别库(例如catscirce等)在内部都使用法律测试来测试其数据类型。

  • Avoid writing specific test-cases for your data-types and functions and try to generalize them using property-based law tests.

    避免为您的数据类型和函数编写特定的测试用例,并尝试使用基于属性的法律测试来概括它们。

Thank you for reaching this far! I am super excited about finding more abstract and useful laws that I can use in my code! Please let me know about any you’ve used or can think of.

谢谢您达成目标! 我为能在代码中使用更多抽象和有用的法律而感到非常兴奋! 请让我知道您已经使用或想到的任何内容。

More inspiring and detailed content can be found in the cats-laws site or circe.

在cat -laws网站或circe上可以找到更多启发性和详细的内容。

The complete code examples can be found here.

完整的代码示例可在此处找到。

翻译自: https://www.freecodecamp.org/news/an-introduction-to-law-testing-in-scala-4243d72272f9/

scala 单元测试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/391667.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

getBoundingClientRect说明

getBoundingClientRect用于获取某个元素相对于视窗的位置集合。 1.语法&#xff1a;这个方法没有参数。 rectObject object.getBoundingClientRect() 2.返回值类型&#xff1a;TextRectangle对象&#xff0c;每个矩形具有四个整数性质&#xff08; 上&#xff0c; 右 &#xf…

robot:接口入参为图片时如何发送请求

https://www.cnblogs.com/changyou615/p/8776507.html 接口是上传图片&#xff0c;通过F12抓包获得如下信息 由于使用的是RequestsLibrary&#xff0c;所以先看一下官网怎么传递二进制文件参数&#xff0c;https://2.python-requests.org//en/master/user/advanced/#post-multi…

开发小Tips-setValue

字典添加数据请用 [dict setValue:value forKey:key] 代替 [dict setObject:value forKey:key] 复制代码这样在一些网络传参时不用考虑nil的情况&#xff0c;?

浏览器快捷键指南_快速但完整的IndexedDB指南以及在浏览器中存储数据

浏览器快捷键指南Interested in learning JavaScript? Get my JavaScript ebook at jshandbook.com有兴趣学习JavaScript吗&#xff1f; 在jshandbook.com上获取我JavaScript电子书 IndexedDB简介 (Introduction to IndexedDB) IndexedDB is one of the storage capabilities …

Java-Character String StringBuffer StringBuilder

Java Character 类 Character 类用于对单个字符进行操作character 类在对象包装一个基本类型char的值 char ch "a";char uniChar \u039A;char[] charArray {a, b, c};使用Character的构造方法创建一个Character类对象 Character ch new Character(a);Charact…

已知两点坐标拾取怎么操作_已知的操作员学习-第3部分

已知两点坐标拾取怎么操作有关深层学习的FAU讲义 (FAU LECTURE NOTES ON DEEP LEARNING) These are the lecture notes for FAU’s YouTube Lecture “Deep Learning”. This is a full transcript of the lecture video & matching slides. We hope, you enjoy this as mu…

缺失值和异常值处理

一、缺失值 1.空值判断 isnull()空值为True&#xff0c;非空值为False notnull() 空值为False&#xff0c;非空值为True s pd.Series([1,2,3,np.nan,hello,np.nan]) df pd.DataFrame({a:[1,2,np.nan,3],b:[2,np.nan,3,hello]}) print(s.isnull()) print(s[s.isnull() False]…

leetcode 503. 下一个更大元素 II(单调栈)

给定一个循环数组&#xff08;最后一个元素的下一个元素是数组的第一个元素&#xff09;&#xff0c;输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序&#xff0c;这个数字之后的第一个比它更大的数&#xff0c;这意味着你应该循环地搜索它的下一个更…

setNeedsDisplay看我就懂!

前言&#xff1a; setNeedsDisplay异步执行的。它会自动调用drawRect方法&#xff0c;这样可以拿到 UIGraphicsGetCurrentContext&#xff0c;就可以绘制了。而setNeedsLayout会默认调用layoutSubViews&#xff0c;处理子视图中的一些数据。 一、着手 我定义了一个UIView的子类…

如何使用ArchUnit测试Java项目的体系结构

by Emre Savcı由EmreSavcı 如何使用ArchUnit测试Java项目的体系结构 (How to test your Java project’s architecture with ArchUnit) In this post, I will show you an interesting library called ArchUnit that I met recently. It does not test your code flow or bu…

解决ionic3 android 运行出现Application Error - The connection to the server was unsuccessful

在真机上启动ionic3打包成的android APK,启动了很久结果弹出这个问题&#xff1a; Application Error - The connection to the server was unsuccessful 可能是我项目资源太多东西了&#xff0c;启动的时间太久了&#xff0c;导致超时了。 解决方案是在项目目录下的config.xml…

特征工程之特征选择_特征工程与特征选择

特征工程之特征选择&#x1f4c8;Python金融系列 (&#x1f4c8;Python for finance series) Warning: There is no magical formula or Holy Grail here, though a new world might open the door for you.警告 &#xff1a; 这里没有神奇的配方或圣杯&#xff0c;尽管新世界可…

搭建Harbor企业级docker仓库

https://www.cnblogs.com/pangguoping/p/7650014.html 转载于:https://www.cnblogs.com/gcgc/p/11377461.html

leetcode 131. 分割回文串(dp+回溯)

给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读都一样的字符串。 示例 1&#xff1a; 输入&#xff1a;s “aab” 输出&#xff1a;[[“a”,“a”,“b”],[“aa”,“b”]]…

[翻译练习] 对视图控制器压入导航栈进行测试

译自&#xff1a;swiftandpainless.com/testing-pus… 上个月我写的关于使用 Swift 进行测试驱动开发的书终于出版了&#xff0c;我会在本文和接下来的一些博文中介绍这本书撰写过程中的一些心得和体会。 在本文中&#xff0c;我将会展示一种很好的用来测试一个视图控制器是否因…

python多人游戏服务器_Python在线多人游戏开发教程

python多人游戏服务器This Python online game tutorial from Tech with Tim will show you how to code a scaleable multiplayer game with python using sockets/networking and pygame. You will learn how to deploy your game so that people anywhere around the world …

版本号控制-GitHub

前面几篇文章。我们介绍了Git的基本使用方法及Gitserver的搭建。本篇文章来学习一下怎样使用GitHub。GitHub是开源的代码库以及版本号控制库&#xff0c;是眼下使用网络上使用最为广泛的服务&#xff0c;GitHub能够托管各种Git库。首先我们须要注冊一个GitHub账号&#xff0c;打…

leetcode132. 分割回文串 II(dp)

给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是回文。 返回符合要求的 最少分割次数 。 示例 1&#xff1a; 输入&#xff1a;s “aab” 输出&#xff1a;1 解释&#xff1a;只需一次分割就可将 s 分割成 [“aa”,“b”] 这样两个回文子串…

数据标准化和离散化

在某些比较和评价的指标处理中经常需要去除数据的单位限制&#xff0c;将其转化为无量纲的纯数值&#xff0c;便于不同单位或量级的指标能够进行比较和加权。因此需要通过一定的方法进行数据标准化&#xff0c;将数据按比例缩放&#xff0c;使之落入一个小的特定区间。 一、标准…

熊猫tv新功能介绍_熊猫简单介绍

熊猫tv新功能介绍Out of all technologies that is introduced in Data Analysis, Pandas is one of the most popular and widely used library.在Data Analysis引入的所有技术中&#xff0c;P andas是最受欢迎和使用最广泛的库之一。 So what are we going to cover :那么我…