zoukankan      html  css  js  c++  java
  • 使用FsCheck编写Property-based测试

    使用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类型。

  • 相关阅读:
    Python入门11 —— 基本数据类型的操作
    Win10安装7 —— 系统的优化
    Win10安装6 —— 系统的激活
    Win10安装5 —— 系统安装步骤
    Win10安装4 —— 通过BIOS进入PE
    Win10安装2 —— 版本的选择与下载
    Win10安装1 —— 引言与目录
    Win10安装3 —— U盘启动工具安装
    虚拟机 —— VMware Workstation15安装教程
    Python入门10 —— for循环
  • 原文地址:https://www.cnblogs.com/xiandnc/p/10501882.html
Copyright © 2011-2022 走看看