編寫基於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供你參考:函數
例如被測函數爲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
例如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
在數據變化過程當中,有一些屬性是永遠不會改變的,例如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測試:
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
迄今爲止,咱們都在使用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下載