赞扬精心设计:基于属性的测试如何帮助我成为更好的开发人员

2023-11-16

开发人员的测试工具箱就是其中之一,很少保持不变。 当然,某些测试实践已被证明比其他测试更有价值,但我们仍在不断寻找更好,更快和更具表现力的方法来测试我们的代码。 基于属性的测试,对于Java社区几乎是未知的,这是Haskell员工精心制作的又一颗瑰宝,在QuickCheck论文中进行了介绍

Scala社区( ScalaCheck库诞生于此)和其他许多人很快就意识到了这种测试技术的强大功能,但是Java生态系统在相当长的一段时间内一直缺乏采用基于属性的测试的兴趣。 幸运的是,自从jqwik出现以来,情况就在慢慢变化,以求更好。

对于许多人来说,很难掌握什么是基于属性的测试以及如何利用它。 杰西卡·克尔( Jessica Kerr)的精彩演讲《基于属性的测试,更好的代码》 ,以及有关基于属性的测试的简介,基于属性的测试模式系列的文章,是吸引您的绝佳来源,但是在今天的帖子中,我们将尝试发现典型的Java开发人员使用jqwik进行的基于属性的测试的实践方面。

首先,基于属性的测试名称实际上意味着什么? 每个Java开发人员首先想到的是它旨在测试所有的getter和setter(您好100%覆盖率)吗? 并非如此,尽管对于某些数据结构而言可能很有用。 相反,我们应该确定组件,数据结构甚至单个功能的高级特征,并通过提出假设有效地对其进行检验。

我们的第一个示例属于“那里又回来”类别:将序列化和反序列化为JSON表示形式。 被测试的类是User POJO ,虽然很简单,但是请注意它具有OffsetDateTime类型的一个时间属性。

 public class User {

    private String username;

    @JsonFormat (pattern = "yyyy-MM-dd'T'HH:mm:ss[.SSS[SSS]]XXX" , shape = Shape.STRING)

    private OffsetDateTime created;
    
    // ...
 }

令人惊讶的是,由于所有人都在尝试使用自己的表示形式,因此如今使用日期/时间属性进行操作的频率有多少会引起问题。 如您所见,我们的合同使用的是ISO-8601互换格式,带有可选的毫秒部分。 我们要确保的是,可以将任何有效的User实例序列化为JSON ,然后反序列化为Java对象,而不会失去任何日期/时间精度。 作为练习,让我们首先尝试用伪代码来表达它:

 For any user

  Serialize user instance to JSON

  Deserialize user instance back from JSON

  Two user instances must be identical

看起来很简单,但令人惊讶的部分出在这里:让我们看一下如何使用jqwik库将此伪代码投影到真实的测试用例中。 它尽可能地接近我们的伪代码。

 @Property
 void @ForAll serdes( @ForAll ( "users" ) User user) throws JsonProcessingException {

    final String json = serdes.serialize(user); 
    assertThat(serdes.deserialize(json))

        .satisfies(other -> {

            assertThat(user.getUsername()).isEqualTo(other.getUsername());

            assertThat(user.getCreated().isEqual(other.getCreated())).isTrue();

        });
        
    Statistics.collect(user.getCreated().getOffset());
 }

测试用例读起来很容易,通常是自然的,但是显然, jqwik@Property@ForAll批注后面隐藏着一些背景。 让我们从@ForAll开始,并清除所有这些User实例的来源。 您可能猜到了,这些实例必须生成,最好以随机方式生成。

对于大多数内置数据类型, jqwik具有一组丰富的数据提供程序(任意),但是由于我们要处理特定于应用程序的类,因此我们必须提供自己的生成策略。 它应该能够发出具有广泛的用户名以及不同时区和偏移量集合的日期/时刻的User类实例。 让我们先来看看提供者的实现,然后再详细讨论。

@Provide Arbitrary<User> users() {

    final Arbitrary<String> usernames = Arbitraries.strings().alpha().ofMaxLength( 64 );
 
    final Arbitrary<OffsetDateTime> dates = Arbitraries

        .of(List.copyOf(ZoneId.getAvailableZoneIds()))

        .flatMap(zone -> Arbitraries

            .longs()

            .between(1266258398000L, 1897410427000L) // ~ +/- 10 years

            .unique()

            .map(epochMilli -> Instant.ofEpochMilli(epochMilli))

            .map(instant -> OffsetDateTime.from(instant.atZone(ZoneId.of(zone)))));

    return Combinators

        .combine(usernames, dates)

        .as((username, created) -> new User(username).created(created));
 }

用户名的来源很简单:只是随机字符串。 日期来源基本上可以是2010年到2030年之间的任何日期/时间,而时区部分(因此是偏移量)是从所有可用的基于区域的区域标识符中随机选择的。 例如,下面是jqwik提供的一些示例。

 { "username" : "zrAazzaDZ" , "created" : "2020-05-06T01:36:07.496496+03:00" }
 { "username" : "AZztZaZZWAaNaqagPLzZiz" , "created" : "2023-03-20T00:48:22.737737+08:00" }
 { "username" : "aazGZZzaoAAEAGZUIzaaDEm" , "created" : "2019-03-12T08:22:12.658658+04:00" }
 { "username" : "Ezw" , "created" : "2011-10-28T08:07:33.542542Z" }
 { "username" : "AFaAzaOLAZOjsZqlaZZixZaZzyZzxrda" , "created" : "2022-07-09T14:04:20.849849+02:00" }
 { "username" : "aaYeZzkhAzAazJ" , "created" : "2016-07-22T22:20:25.162162+06:00" }
 { "username" : "BzkoNGzBcaWcrDaaazzCZAaaPd" , "created" : "2020-08-12T22:23:56.902902+08:45" }
 { "username" : "MazNzaTZZAEhXoz" , "created" : "2027-09-26T17:12:34.872872+11:00" }
 { "username" : "zqZzZYamO" , "created" : "2023-01-10T03:16:41.879879-03:00" }
 { "username" : "GaaUazzldqGJZsqksRZuaNAqzANLAAlj" , "created" : "2015-03-19T04:16:24.098098Z" }
 ...

默认情况下, jqwik将针对1000套不同的参数值(随机用户实例)运行测试。 非常有用的“统计”容器允许收集您好奇的任何分布见解。 以防万一,为什么不按区域偏移量收集分布呢?

 ...

    - 04 : 00 ( 94 ) : 9.40 %

    - 03 : 00 ( 76 ) : 7.60 %

    + 02 : 00 ( 75 ) : 7.50 %

    - 05 : 00 ( 74 ) : 7.40 %

    + 01 : 00 ( 72 ) : 7.20 %

    + 03 : 00 ( 69 ) : 6.90 %

    Z     ( 62 ) : 6.20 %

    - 06 : 00 ( 54 ) : 5.40 %

    + 11 : 00 ( 42 ) : 4.20 %

    - 07 : 00 ( 39 ) : 3.90 %

    + 08 : 00 ( 37 ) : 3.70 %

    + 07 : 00 ( 34 ) : 3.40 %

    + 10 : 00 ( 34 ) : 3.40 %

    + 06 : 00 ( 26 ) : 2.60 %

    + 12 : 00 ( 23 ) : 2.30 %

    + 05 : 00 ( 23 ) : 2.30 %

    - 08 : 00 ( 20 ) : 2.00 %

    ...

让我们考虑另一个例子。 想象一下,我们决定基于用户名属性重新实现User类的相等性(在Java中,这意味着重写equalshashCode )。 这样,对于任何一对User类实例,以下不变量必须为true:

  • 如果两个User实例具有相同的用户名,则它们相等,并且必须具有相同的哈希码
  • 如果两个User实例具有不同的username ,则它们不相等(但哈希码不一定相同)

它非常适合基于属性的测试,并且jqwik尤其使这种测试的编写和维护变得微不足道。

 @Provide
 Arbitrary&ltString> usernames() {

    return Arbitraries.strings().alpha().ofMaxLength( 64 );
 }
 @Property
 void equals( @ForAll ( "usernames" ) String username, @ForAll ( "usernames" ) String other) {

    Assume.that(!username.equals(other));
        
    assertThat( new User(username))

        .isEqualTo( new User(username))

        .isNotEqualTo( new User(other))

        .extracting(User::hashCode)

        .isEqualTo( new User(username).hashCode());
 }

由于我们引入了用户名的两个来源,因此通过假设表达的假设允许对生成的参数施加额外的约束,这可能会发生,因为它们在同一运行中都发出相同的用户名,因此测试将失败。

您到目前为止可能遇到的问题是:重点是什么? 当然可以在不进行基于属性的测试和使用jqwik的情况下测试序列化/反序列化或equals / hashCode ,那么为什么还要麻烦呢? 足够公平,但是这个问题的答案基本上取决于我们如何处理软件系统的设计。

总的来说,基于属性的测试在很大程度上受函数式编程的影响,温和地说,这并不是Java的第一件事(至少现在还不是)。 随机生成测试数据本身并不是一个新颖的主意,但是至少基于我的观点,基于属性的测试鼓励您去做的是,以更抽象的角度思考,而不是专注于单个操作(等于,比较,加法)。 ,排序,序列化...),但是它们要遵循什么样的属性,特征,定律和/或不变式。 当然,这感觉就像是一种外来技术,如果您愿意的话,可以改变范式,鼓励您花更多的时间设计正确的东西。 这并不意味着从现在起您的所有测试都必须基于属性,但我相信它当然应该在我们的测试工具箱的前排中占据一席之地。

请在Github上找到完整的项目资源。

翻译自: https://www.javacodegeeks.com/2020/02/in-praise-of-the-thoughful-design-how-property-based-testing-helps-me-to-be-a-better-developer.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

赞扬精心设计:基于属性的测试如何帮助我成为更好的开发人员 的相关文章

  • 我可以在pycharm中的断点处进入交互模式吗

    我是一个相当新的 Pycharm 3 用户 正在从事 django 项目 我可以在 pycharm3 中的断点处进入交互模式吗 这可能吗 当程序在断点处停止时 我尝试过工具 gt 打开调试命令行 但我没有看到控制台打开 我怎样才能让它发挥作
  • 如何使用Peewee查询多个相似的数据库?

    我遇到了使用 Peewee 查询多个数据库的问题 我有 2 个现有的 mysql 数据库 让我们将它们命名为 A 和 B 结构相似 因为它是两个 Bugzilla 数据库 我使用 Pwiz 生成模型 modelsA py 和 modelsB
  • Netty中连接关闭后重新连接的最佳方法是什么

    简单场景 扩展 SimpleChannelUpstreamHandler 的较低级别的类 A 此类是发送消息和接收响应的主力 系统其他部分可以使用顶级类 B 来发送和接收消息 可以模拟同步和异步 此类创建 ClientBootstrap 设
  • pandas groupby 并转换为 json 列表

    我有一个如下所示的 pandas 数据框 idx f1 f2 f3 1 a a b 2 b a c 3 a b c 87 e e e 我需要将其他列转换为基于索引列的字典列表 所以 最终结果应该是 idx features 1 f1 a f
  • Hibernate @OneToMany 注释到底是如何工作的?

    我对 Hibernate 还很陌生 我正在通过教程学习它 我在理解到底如何一对多注释作品 所以我有这两个实体类 Student代表一个学生并且Guide代表指导学生的人 因此 每个学生都与一名向导相关联 但一名向导可以跟随多个学生 我想要一
  • 无法使用 wxPython 打开在 folium 中生成的本地 HTML 文件

    我目前正在尝试将 GPS 坐标绘制为地图上的标记 并在 wxPython 中显示结果 我使用 folium 绘制坐标标记并生成 HTML 文件 import folium fmap folium Map 43 5321 172 6362 z
  • Python 中的 Firebase 身份验证时出现 KeyError:“databaseURL”

    相信你做得很好 我是 firebase 的新手 正在尝试进行用户身份验证 我已经安装了pyrebase4并在firebase控制台上创建了一个项目 我还启用了使用 电子邮件和密码 登录并尝试连接我的应用程序 下面是我正在尝试的代码 impo
  • 如何将任务添加到 gradle 中的主要“构建”任务

    当我尝试使用以下代码将任务添加到主构建任务时 rootProject tasks getByName build dependsOn mytask 当我跑步时它抱怨gradle w build输出 Where Build file line
  • Python-使用元组作为列表索引[重复]

    这个问题在这里已经有答案了 我有一个元组列表 tuples list 1 0 2 3 3 2 2 0 我想访问二维数组的元素a例如 使用其中一些元组 for i in range 3 print a tuples list i 应该输出的值
  • 设计抽象类时是否应该考虑序列化问题?

    一般来说这个问题来自Eclipse建议在抽象类上添加串行版本UID 由于该类是抽象类 因此该类的实例永远不会存在 因此它们永远不会被序列化 只有派生类才会被序列化 所以我的问题是放置一个安全 SuppressWarnings serial
  • Django - 渲染到字符串无法加载 CSS

    我正在尝试使用 Django 1 8 render to string 通过管理命令将 html 转换为 pdf 而不是使用 View request 以下代码可以将模板转换为 pdf 但它无法将 CSS 加载到模板中 def html t
  • JPA - 非主键字段上的 @OneToOne 关系不起作用

    我有一个 Spring Data JPA 后端 使用 Hibernate 作为 ORM 实现 这是模型 Person MailConfig id PK uid PK FK Person uid uid Entity
  • 使用 Python 获取 Youtube 数据

    我正在尝试学习如何分析网络上可用的社交媒体数据 我从 Youtube 开始 from apiclient errors import HttpError from outh2client tools import argparser fro
  • 如何将 Django 数据库中的模板标签解释/渲染为 HTML

    我正在尝试添加带有来自 Django 管理站点的图像的帖子 但安全 自动转义关闭过滤器无法解释 Django 的模板标签 My input and page look like 复制图像地址 给出http 127 0 0 1 8000 7B
  • Java中的媒体播放器库[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在评估用于在 Java 中播放音频 视频的库 它不需要 100 Java Java 与本机库的绑定
  • 如何从 PyObject 获取指向字符串的 char*

    我怎样才能得到一个char from a PyObject它指向一个字符串 例如 这是 python 脚本 Test Connect 272 22 20 65 1234 这是 C 代码 static PyObject Connect PyO
  • java数据结构模拟数据树

    我需要帮助定义使用什么方法 我有一个 SOAP 响应 给我一个 xml 文件 我需要在屏幕上显示 3 个相关列表 当您在第一个列表中选择一个项目时 相应的选择将出现在第二个列表中 依此类推 我只对从 xml 流中提取数据后如何有效地组织数据
  • 重写 __cmp__ python 函数

    嗨 我是压倒一切的 cmp 如果传递的第二个对象是None 或者如果它不是一个实例someClass 然后返回 1 我不明白这里到底发生了什么 class someClass def cmp self obj if obj None ret
  • 我可以以某种方式“编译”一个Python脚本以在没有安装Python的PC上运行吗?

    所以我有一个Python脚本 myscript py 我是这样执行的 python D myscript py 但是 我必须安装 Python 并将其包含在PATH使其工作的环境变量 是否有可能以某种方式将 Python 可执行文件与 Py
  • java中的回调是什么[重复]

    这个问题在这里已经有答案了 可能的重复 什么是回调函数 https stackoverflow com questions 824234 what is a callback function 我已经阅读了回调的维基百科定义 但我仍然没有明

随机推荐

  • Nginx基础知识(个人总结)

    声明 1 本文为我的个人复习总结 并非那种从零基础开始普及知识 内容详细全面 言辞官方的文章 2 由于是个人总结 所以用最精简的话语来写文章 3 若有错误不当之处 请指出 Nginx C语言编写 战斗民族俄罗斯人所创 是高性能的 HTTP
  • Java学习之笔试面试题总结

    1 浅复制 浅克隆 被复制对象的所有变量都含有与原来的对象相同的值 而所有的对其他对象的引用仍然指向原来的对象 换言之 浅复制仅仅复制所考虑的对象 而不复制它所引用的对象 深复制 深克隆 被复制对象的所有变量都含有与原来的对象相同的值 除去
  • ctfshow-web3

    0x00 前言 CTF 加解密合集 CTF Web合集 0x01 题目 0x02 Write Up 这个题目一看就知道是一个文件包含漏洞 php input可以访问请求的原始数据的只读流 将post请求的数据当作php代码执行 GET ht
  • 【Dexie.js 踩坑】Failed to execute ‘transaction‘ on ‘IDBDatabase‘

    查了很多资料 显示都是 indexedDB 的报错 说是异步操作无法保证下一次操作时上一步已经完成 试了很多按顺序执行的方法都无效 再后来试着解决控制台显示的警告提示我版本控制有问题 我就把版本升级了 问题迎刃而解 Dexie 官方文档是英
  • LayuiAdmin模板(0积分免费下载,非单独框架)

    点击这里直接下LayuiAdmin模板 请选择 普通下载
  • 4.通过Opencv采集摄像头视频数据

    VideoCapture 虚拟采集器 一般设备号从0开始 cap read 读取视频帧 返回值有两个 第一个为状态值 读到帧为true 第二个值为视频帧 cap release 释放资源 示例程序 import cv2 引入CV库 创建窗口
  • 深度干货:制造进销存国内现状如何?2023年五大制造进销存最新盘点!

    制造进销存是什么 制造进销存的发展如何 制造进销存的优势在哪里 制造进销存都能为企业提供什么 本文将带大家深入浅出的聊聊制造进销存 全面剖析制造进销存的前世今生 并且为大家提供2023年十大制造进销存系统大盘点 一 什么是制造进销存 制造进
  • W801

    目录 文档来源 芯片概述及MCU 特性可参考 安全特性 Wi Fi 特性 蓝牙特性 低功耗模式 芯片结构 与前述介绍的外设相对应 总线结构 说明 1 AHB 1 总线 主设备列表 编辑 从设备列表 2 AHB 2总线 时钟结构 CDK中对运
  • linux入门系列18--Web服务之Apache服务2

    接上一篇文章 在了解Apache基本配置以及SELinux相关知识后 继续演示Apache提供的虚拟主机功能以及访问控制方式 如果还没看上一篇的建议先查看后再来 上篇文章 linux入门系列18 web服务之apache服务1 三 Apac
  • openwrt调试用到的

    PC间文件共享 python3 m http server 在共享的电脑上 打开浏览器 直接输入 serverip 8000 NFS Ubuntu PC端 sudo apt get install nfs kernel server sud
  • shell的模拟实现

    目录 整体框架分析 代码演示 代码分析 整体框架分析 考虑下面这个与shell典型的互动 xzy ecs 333953 date16 ls makefile mycmd mycmd cpp myexec myexec c test py x
  • Python爬虫,私活接单记录,假日到手5500,美滋滋

    前言 每年的元旦节前后 都会是Python兼职接单的小高潮 这段时间各个行业对爬虫类的需求会暴增 圈子里很多朋友在元旦假期都没闲着 两天赚上万的不在少数 所以近来问我技术变现 兼职接单问题的朋友也特别多 我把问题总结下来 发现大部分人都有着
  • dataframe中如何筛选包含特定字符串(单个字符串、字符串列表)的列?

    里斯斯里 dataframe中如何筛选包含特定字符串 单个字符python基础教程 串 字符串列表 的列 问题描述 dataframe的某一列均为字符串格式 想筛选出含有特定字符串的行 具体实现代码如下 df df 地址 str conta
  • React循环

    import React Component from react class App extends Component constructor props super props this state str 这是react数据 num
  • golang之json注释处理

    场景 json 作为现代比较常用的文本格式 本身是不支持注释的 因为它的设计初衷是作为一种轻量级数据交换格式 只需要包含数据本身 而不应该包含注释或者其他无关的信息 但是有时json内字段较多 想写一些注释说明 这些都是编程工具或者编辑器特
  • Qt Assistant使用心得

    Qt Assistant也就是Qt助手 1 如何添加文档 编辑 gt 首选项 gt 文档 gt 添加后选择需要添加的 qch文件 qch是Qt帮助文档的格式 通常Qt的帮助文档在qt目录下ducuments文件夹里 比如我的在 opt V
  • 什么是误报?如何识别误报和漏报

    不管开发人员技能多么精通 误报和漏报总是会发生 很可能是他们的代码有某种无意的错误或漏洞 为了确保尽早发现这些编码错误和漏洞 开发人员通常使用代码静态分析工具 工具会根据开发人员设置的规则检查代码 然而 代码静态分析工具并不完美 工具有时也
  • C++总结:C++中的const和constexpr

    C 总结 C 中的const和constexpr 2014 02 18 15 31 by 付哲 10196 阅读 0 评论 收藏 编辑 C 中的const可用于修饰变量 函数 且在不同的地方有着不同的含义 现总结如下 const的语义 C
  • 编写测试用例方法之边界值分析法

    今天我们再来介绍另外一个编写测试用例的方法 边界值分析法 这个方法也是比较常用的写测试用例的方法 话不多说 开始整干货 首先 全图镇楼 之前我们也说过了 从测试点到测试用例 中间要有专业的方法 并对测试点进行扩充 然后才能详细地把测试点说清
  • 赞扬精心设计:基于属性的测试如何帮助我成为更好的开发人员

    开发人员的测试工具箱就是其中之一 很少保持不变 当然 某些测试实践已被证明比其他测试更有价值 但我们仍在不断寻找更好 更快和更具表现力的方法来测试我们的代码 基于属性的测试 对于Java社区几乎是未知的 这是Haskell员工精心制作的又一