• 在Xunit中使用FsCheck


    目录

    编写基于Property-based的单元测试
    使用FsCheck编写Property-based测试
    在Xunit中使用FsCheck
    使用FsCheck编写Model-based测试-待续

    无论是Xunit还是Nunit都有额外的扩展用来编写FsCheck测试,以Xunit为例 :

    Install-Package FsCheck.Xunit -Version 2.13.0
    

    不同于普通的Xunit测试,一般的测试需要标记[Fact],你需要使用[Property]标记FsCheck测试。给定一个函数:

    private int Add(int x, int y)
    {
        return x + y;
    }
    

    针对加法交换律编写一个Property-based测试:

    [Property]
    public bool Commutative(int x, int y)
    {
        return Add(x, y) == Add(y, x);
    }
    

    F#

    [<Property>]
    let Commutative x y =
        add x y = add y x
    

    在之前的例子里,我们介绍了什么是Property-based测试,然后花了一篇博客介绍了各种各样的Generator,每一个刚开始了解Property-based测试的人都会觉得这种方案很有意思,但是当你真正开始编写Property-base测试的时候你就会感觉得无从下手,应该断言什么样的Properties呢?
    这篇文章介绍一些Properties供你参考:

    1. 不同的执行顺序,同样的执行结果

    例如被测函数为List.OrderBy,如果我们在List.OrderBy函数之前执行一个操作Add1,然后执行List.OrderBy函数。结果应该等于先执行List.OrderBy函数再执行操作Add1

     [Property]
    public bool AddOneThenSortShouldSameAsSortThenAddOne(List<int> list)
    {
        var result1 = list.OrderBy(x => x).Select(Add1);
        var result2 = list.Select(Add1).OrderBy(x => x);
    
        return result1.SequenceEqual(result2);
    }
    

    F#

     [<Property(Verbose=true)>]
    let ``+1 then sort should be same as sort then +1`` aList =
        let add1 x = x + 1
        
        let result1 = aList |> List.sort |> List.map add1
        let result2 = aList |> List.map add1 |> List.sort
        
        result1 = result2
    

    2.连续执行操作,结果跟之前一致

    例如List.Reverse函数,连续执行两次,结果跟期初是一样的。类似的函数如序列化和反序列化,Redo和Undo。

    [Property]
    public bool ReverseThenReverseShouldSameAsOriginal(int[] list)
    {
      var result=  list.Reverse().Reverse();
      return result.SequenceEqual(list);
    }
    

    F#

    [<Property>]
    let ``reverse then reverse should be same as original`` 
    (aList:int list) =
        let reverseThenReverse = aList |> List.rev |> List.rev
        reverseThenReverse = aList
    

    3. 有一些属性是永远不会改变的

    在数据变化过程中,有一些属性是永远不会改变的,例如Sort操作,前后数据的Length总是不变的,这一属性可以作为Property-based测试的一个依据:

    public bool SomethingNeverChanged(List<int> list)
    {
        var result = list.OrderBy(x => x);
        return result.Count() == list.Count;
    }
    

    F#

    let ``sort should have same length as original`` (aList:int list) =
        let sorted = aList |> List.sort 
        List.length sorted = List.length aList
    

    为OO代码编写Property-based测试

    接下来我们尝试针对一个OO的例子编写Property-based测试:

    public class Dollar
    {
        private int _amount;
    
        public Dollar(int amount)
        {
            _amount = amount;
        } 
        
        public int Amount => _amount;
        
        public void Add(int add)
        {
             _amount = _amount + add;
        }
        
        public void Multiplier(int multiplier)
        {
            _amount = _amount * multiplier;
        }
        
        public static Dollar Create(int amount)
        {
            return new Dollar(amount);
        }
    }
    

    F#

    type Dollar(amount : int) =
        let mutable privateAmount = amount;
    
        member this.Amount = privateAmount
        member this.Add add =
            privateAmount <- this.Amount + add
        member this.Times multiplier =
            privateAmount <- this.Amount * multiplier
        static member Create amount =
            Dollar amount
    

    Dollar类主要有两个方法,Add和Multiplier分别用来修改私有变量_amount。如何测试Dollar类呢?都有哪些Properties可用?调用Add方法后再读取Amount的值应该是同一个值:

    [Property]
    public bool SetAndGetShouldGiveSameResult(int amount)
    {
        var dollar = Dollar.Create(0);
        dollar.Add(amount);
    
        return dollar.Amount == amount;
    }
    

    F#

    [<Property>]
    let ``set then get should give same result`` value =
        let obj = Dollar.Create 0
        obj.Add value
        let newValue = obj.Amount
        value = newValue 
    

    还有什么Property可供使用呢,Add和Multiplier两个方法执行完毕的结果等价于直接Create:

    [Property]
    public bool AddThenMultiplierSameAsCreate(int start, int times)
    {
        var dollar = Dollar.Create(0);
        dollar.Add(start);
        dollar.Multiplier(times);
    
        var dollar2 = Dollar.Create(start * times);
    
        return dollar.Amount == dollar2.Amount;
    }
    

    F#

     [<Property>]
    let ``add then multiplier same as create`` value times =
        let dollar = Dollar.Create 0
        dollar.Add value
        dollar.Times times
        
        let dollar2 = Dollar.Create(value*times);
        
        dollar.Amount = dollar2.Amount
    

    编写自定义Generator

    迄今为止,我们都在使用FsCheck自带的Generator,而在实际项目开发过程中,你还需要生成自定义的Generator供你使用,例如有一个User类型:

    public class User
    {
        public string Name { get; set; }
        
        public int Age { get; set; }
    }
    

    自定义Generator:

    public class UserArbitrary: Arbitrary<User>
    {
        public override Gen<User> Generator =>
            from x in Arb.Generate<string>()
            from int y in Gen.Choose(20, 30)
            where x != string.Empty
            select new User {Name = x, Age = y};
    }
    

    最后还要将自定义的Arbitrary注册在FsCheck中:

    public class MyGenerators {
        public static Arbitrary<User> User() {
            return new UserArbitrary();
        }
    }
    
    Arb.Register<MyGenerators>();
    

    写个例子试试:

    [Property]
    public bool GenerateUsers(User user)
    {
       return user.Name != string.Empty;
    }
    

    所以代码实例均可以在github下载

  • 相关阅读:
    云计算分布式大数据神器Spark实战高手之旅
    Spring IOC及AOP学习总结
    Win7下不能查看xp系统共享的文件,解决方法
    c#怎样获取excel单元格的RGB颜色
    MySQL 全角转换为半角
    【剑指offer】旋转数组的最小值
    POJ 2524 :Ubiquitous Religions
    GitLal+sourceTree版本号管理
    ASP.NET MVC 过滤器(五)
    Java设计模式之观察者模式
  • 原文地址:https://www.cnblogs.com/xiandnc/p/10506440.html
Copyright © 2020-2023  润新知