在Xunit中使用FsCheck

目錄

編寫基於Property-based的單元測試
使用FsCheck編寫Property-based測試
在Xunit中使用FsCheck
使用FsCheck編寫Model-based測試-待續html

不管是Xunit仍是Nunit都有額外的擴展用來編寫FsCheck測試,以Xunit爲例 :java

Install-Package FsCheck.Xunit -Version 2.13.0

不一樣於普通的Xunit測試,通常的測試須要標記[Fact],你須要使用[Property]標記FsCheck測試。給定一個函數:git

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

針對加法交換律編寫一個Property-based測試:github

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

F#ide

[<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。this

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

F#code

[<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下載

相關文章
相關標籤/搜索