在Xunit中使用FsCheck
目录
目录
为OO代码编写Property-based测试
编写自定义Generator
目录
编写基于Property-based的单元测试
使用FsCheck编写Property-based测试
在Xunit中使用FsCheck
使用FsCheck编写Model-based测试-待续
无论是Xunit还是Nunit都有额外的扩展用来编写FsCheck测试,以Xunit为例 :
1 | Install-Package FsCheck.Xunit -Version 2.13.0 |
不同于普通的Xunit测试,一般的测试需要标记[Fact],你需要使用[Property]标记FsCheck测试。给定一个函数:
1 2 3 4 | private int Add( int x, int y) { return x + y; } |
针对加法交换律编写一个Property-based测试:
1 2 3 4 5 | [Property] public bool Commutative( int x, int y) { return Add(x, y) == Add(y, x); } |
F#
1 2 3 | [<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
1 2 3 4 5 6 7 8 | [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#
1 2 3 4 5 6 7 8 | [<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。
1 2 3 4 5 6 | [Property] public bool ReverseThenReverseShouldSameAsOriginal( int [] list) { var result= list.Reverse().Reverse(); return result.SequenceEqual(list); } |
F#
1 2 3 4 5 | [<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测试的一个依据:
1 2 3 4 5 | public bool SomethingNeverChanged(List< int > list) { var result = list.OrderBy(x => x); return result.Count() == list.Count; } |
F#
1 2 3 | 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测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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#
1 2 3 4 5 6 7 8 9 10 | 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的值应该是同一个值:
1 2 3 4 5 6 7 8 | [Property] public bool SetAndGetShouldGiveSameResult( int amount) { var dollar = Dollar.Create(0); dollar.Add(amount); return dollar.Amount == amount; } |
F#
1 2 3 4 5 6 | [<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:
1 2 3 4 5 6 7 8 9 10 11 | [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#
1 2 3 4 5 6 7 8 9 | [<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类型:
1 2 3 4 5 6 | public class User { public string Name { get ; set ; } public int Age { get ; set ; } } |
自定义Generator:
1 2 3 4 5 6 7 8 | 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中:
1 2 3 4 5 6 7 | public class MyGenerators { public static Arbitrary<User> User() { return new UserArbitrary(); } } Arb.Register<MyGenerators>(); |
写个例子试试:
1 2 3 4 5 | [Property] public bool GenerateUsers(User user) { return user.Name != string .Empty; } |
所以代码实例均可以在github下载
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?