使用FsCheck编写Property-based的测试

编写基于Property-based的单元测试一文中,我们介绍了什么是Property-based测试。同时我们也总结了Property-based测试的两个策略:

  • 随机产生若干个输入值,保证足够多的测试用例
  • 断言被测代码具有普遍适应性的属性

FsCheck是一个F#版本的QuickCheck移植版本,本文将介绍如何使用FsCheck。

初识FsCheck

FsCheck是一个用来编写Property-based测试的工具,开发者通过总结和归纳代码满足的属性(Properties),利用FsCheck生成大量随机的输入对总结的属性进行验证。FsCheck提供了一系列方式让你组合各类属性,同时还提供了各种数据类型的生成器。

新建一个Console应用程序,添加Nuget:

Install-Package FsCheck -Version 2.13.0

编写一个简单的测试case:

static void Main(string[] args)
{
Prop.ForAll<int>(x => x != x + 1 )
.QuickCheck("Number always not equal self add 1"); Console.ReadKey();
}

我们定义了一个永远都为true的Property: x != x + 1,如果把这个代码跑起来,Console里会输出下面的内容:

Number always not equal self add 1-Ok, passed 100 tests.

FsCheck随机产生了100个输入,并且这个100个测试都通过了, 我们稍微修改下上面的代码,将QuickCheck方法改为VerboseCheck,让Console输出更加详细的日志:

static void Main(string[] args)
{
Prop.ForAll<int>(x => x != x + 1)
.VerboseCheck("Number always not equal self add 1");
Console.ReadKey();
}

这次Console会打出100个随机的输入。

利用Generator创建测试数据

FsCheck提供了一套用于创建随机数据的方式,分别为Generator,Shrinker,Arbitrary:

Generator用于创建随机数据,FsCheck已经定义了一些用于生成基本类型的Generator,当然你还可以自定义Generator,用来生成任意类型的随机数据。

Generator是一个Gen类型,用户生成T类型的随机数据,下面是一个最基本的例子,创建了一个Constant类型的Generator,通过Gen.Sample方法创建5个字符串:

var gen = Gen.Constant("foo");
var strings = Gen.Sample(10, 5, gen);

F#:

let constantStrings = Gen.constant("Foo") |> Gen.sample 0 5

Constant类型的Genrator算是最简单的Genrator,用于生成同一个值,在上面的例子中,将会生成具有5个元素的list:

["foo"; "foo"; "foo"; "foo"; "foo"]

因为FsCheck的功能是生成随机数据,所以Constant类型的Genrator其实不太常用,但是非常便于理解Genrator的作用。

Choose

var gen = Gen.Choose(1, 10);
var value = Gen.Sample(0, 5, gen);

F#:

 Gen.choose(1, 10) |> Gen.sample 0 5

choose函数接受一个最小值和最大值,创建的Generator在最小值和最大值之间生成数字,上面的例子中分别为1和10。Gen.Sample函数接受三个参数,用于使用某一个Generator创建一组样本,第一个参数0在本例中不起作用,5表示生成5个值,生成的数据为:

[2; 7; 10; 7; 7]

Elements

Gen.Elements用于从一组元素列表中创建一个Generator,从提供的元素列表中选取随机值,例如下面的实例:

var gen = Gen.Elements(42, 1337, 7, -100, 1453, -273);
var value = Gen.Sample(0, 5, gen);

F#:

Gen.elements [42; 1337; 7; -100; 1453; -273] |> Gen.sample 0 10

在上面的例子中,先定义了5个元素,Generator会随机从这5个元素中选取值,最后生成的结果如下:

[1453; 1337; 7; -273; 42; -100; 1453; 1337; 7; -273]

GrowingElements

Gen.GrowingElements跟Gen.Elements特别像,只有一个区别, Gen.Sample函数的第一个参数会起作用,例如定义下面的元素列表:

var gen = Gen.GrowingElements(new List<char>
{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'});
var value = Gen.Sample(3, 5, gen);

F#:

 Gen.growingElements ['a'; 'b'; 'c'; 'd'; 'e'; 'f'; 'g'; 'h'; 'i'; 'j']
|> Gen.sample 3 10

Gen.Sample(3, 5, gen)意味着要生成5个值,并且每个值只能在第三个(‘c'),包括第三个之内,生成的数据如下:

['a'; 'a'; 'b'; 'b'; 'c'; 'c'; 'a'; 'a'; 'a'; 'c']

Map

Gen.Map是一个投影函数,正如List类型一样,Gen类型也可以直接通过Select方法将T类型映射成别的类型。

 var gen = Gen.Choose(1, 30)
.Select(x => new DateTime(2019, 11, x).ToString("u"));
var value = Gen.Sample(0, 10, gen);

F#:

Gen.choose (1, 3) |> Gen.map (fun i -> DateTime(2019, 11, i).ToString "u")
|> Gen.sample 0 10

上面的例子先通过Gen.choose生成1到30日期的随机数,然后在映射为日期。生成的数据如下:

["2019-11-24 00:00:00Z"; "2019-11-15 00:00:00Z"; "2019-11-28 00:00:00Z";
"2019-11-19 00:00:00Z"; "2019-11-02 00:00:00Z"; "2019-11-23 00:00:00Z";
"2019-11-06 00:00:00Z"; "2019-11-27 00:00:00Z"; "2019-11-10 00:00:00Z";
"2019-11-24 00:00:00Z"]

List

除了上面的Generator能够生成一组随机值,你还可以通过Gen.listOf, Gen.ListOfLength, Gen.NonEmptyListOf生成List元素,例如你可以通过Gen.Constant来生成一组包含同一个常量的List。

var gen = Gen.Constant(42).ListOf(1);
var value = Gen.Sample(0, 10, gen);

F#

Gen.constant 42 |> Gen.listOf |> Gen.sample 1 10

上面的例子使用Gen.Constant 42来作为每一个List元素的Generator,通过这种方式生成的lists只包含42。生成的数据如下:

[[42]; [42]; [42]; [42]; [42]; [42]; [42]; [42]; [42]; [42]]

除了使用Gen.ListOf,你还可以通过Gen.NoEmptyListOf来生成至少包含有一个元素的lists:

var gen = Gen.Elements("foo", "bar", "baz")
.NonEmptyListOf();
var value = Gen.Sample(3, 4, gen);

F#

Gen.elements ["foo"; "bar"; "baz"] |> Gen.nonEmptyListOf |> Gen.sample 3 4

生成的输入如下:

[["foo"; "bar"; "baz"], ["foo"], ["baz", "bar"]]

Filter

你已经可以通过上面的Generator来生成各种各样的数据了,你还可以通过Gen.Filter进行过滤,例如下面的例子:

var gen = Gen.Choose(1, 100)
.Two()
.Where(x => x.Item1 != x.Item2)
.Select(x => new List<int> {x.Item1, x.Item2});
var value = gen.Sample(0, 10);

F#

Gen.choose (1, 100)
|> Gen.two
|> Gen.filter (fun (x, y) -> x <> y)
|> Gen.map (fun (x, y) -> [x; y])
|> Gen.sample 0 10

生成的数据如下:

[[30; 89]; [12; 82]; [66; 47]; [82; 40]; [64; 5]; [18; 35]; [61; 42]; [14; 29];
[83; 93]; [100; 37]]

掌握了Generator,下一篇将介绍Shrinker和自定义Arbitrary类型。

使用FsCheck编写Property-based测试的更多相关文章

  1. 20162311 编写Android程序测试查找排序算法

    20162311 编写Android程序测试查找排序算法 一.设置图形界面 因为是测试查找和排序算法,所以先要有一个目标数组.为了得到一个目标数组,我设置一个EditText和一个Button来添加数 ...

  2. Rspec: everyday-rspec实操: 第9章 快速编写测试,编写快速的测试。

    Make it work, make it right, make it fast. 测试运行的时间.应用和测试组件的增长,速度会越来越慢,目标是保持代码的readable, maintainable ...

  3. [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being

    [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent c ...

  4. vue报错 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's

    [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent c ...

  5. 【cypress】3. 编写第一个测试

    当环境安装好了之后,就可以着手尝试第一个测试的编写了. 一.新建一个文件 在你的项目下的cypress/integration文件夹中创建一个新文件sample_spec.js,我这里直接在webst ...

  6. 在Jmeter中使用自定义编写的Java测试代码

    我们在做性能测试时,有时需要自己编写测试脚本,很多测试工具都支持自定义编写测试脚本,比如LoadRunner就有很多自定义脚本的协议,比如"C Vuser","Java ...

  7. Appium之编写H5应用测试脚本(切换到Webview)

    App使用H5编写,默认方式找不到元素.启动后获取所有上下文,找到webivew_xxxx的,然后进行切换. 源码: package MyAppium; import io.appium.java_c ...

  8. 阶段3 3.SpringMVC·_07.SSM整合案例_07.ssm整合之编写MyBatis框架测试保存的方法

    再写一个测试的方法,测试save保存的方法 需要提交事务才能保存到数据库

  9. WCF编写时候的测试

    1右击WCF创建到使用到发布这篇文章中的类库项目中的接口类实现文件添加断点 2右击WCF创建到使用到发布这篇文章中的WCF服务网站设为启动项并允许 3右击WCF创建到使用到发布这篇文章中的WPF项目调 ...

随机推荐

  1. MYSQL—— year类型的使用与注意点!

    mysql的日期与时间类型:分为time.date.datetime.timestamp.year,主要总结下year的用法: 1.类型支持:year 与 year(4),注意无year(2)的定义方 ...

  2. 434个H5游戏源码

    各种类型HTML5游戏,界面和JS均可供项目参考 下面是下载地址

  3. 十分钟学会Java8的lambda表达式和Stream API

    01:前言一直在用JDK8 ,却从未用过Stream,为了对数组或集合进行一些排序.过滤或数据处理,只会写for循环或者foreach,这就是我曾经的一个写照. 刚开始写写是打基础,但写的多了,各种乏 ...

  4. socketserver实现并发

    socketserver实现并发原理:给每一个前来链接的客户端开启一个线程执行通信.也就是给每一个连接“配备”了一个管家. 下面用一个简单的示例来演示socketserver实现并发(一个服务端,两个 ...

  5. 3D数学 矩阵常用知识点整理

    1.矩阵了解 1)矩阵的维度和记法 (先数多少行,再数多少列) 2)矩阵的转置 行变成列,第一行变成第一列...矩阵的转置的转置就是原矩阵            即        3)矩阵和标量的乘法 ...

  6. HWPushDemo【华为推送集成,基于2.6.1.301版本】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 这个Demo只是记录华为推送的集成,不能运行. 另外,因为可能用到存储权限,所以还需要搭配运行时权限申请功能. 使用步骤 一.项目组 ...

  7. 【Python3爬虫】用Python中的队列来写爬虫

    一.写在前面 当你看着你的博客的阅读量慢慢增加的时候,内心不禁有了些小激动,但是不得不吐槽一下--博客园并不会显示你的博客的总阅读量是多少.而这一篇博客就将教你怎么利用队列这种结构来编写爬虫,最终获取 ...

  8. 补习系列(18)-springboot H2 迷你数据库

    目录 关于 H2 一.H2 用作本地数据库 1. 引入依赖: 2. 配置文件 3. 样例数据 二.H2 用于单元测试 1. 依赖包 2. 测试配置 3. 测试代码 小结 关于 H2 H2 数据库是一个 ...

  9. asp.net core 系列之webapi集成EFCore的简单操作教程

    因为官网asp.net core webapi教程部分,给出的是使用内存中的数据即 UseInMemoryDatabase 的方式, 这里记录一下,使用SQL Server数据库的方式即 UseSql ...

  10. android消息处理源码分析

    一.简介消息处理机制主要涉及到这几个类:1.Looper2.MessageQueue3.Message4.Handler 二.源码分析 Looper.class的关键源码: //保存Looper对象, ...