属性-based-test 用于简单的对象验证
property-based-test for simple object validation
考虑这个简单的例子:
- 有一个
Person
对象
- 它必须具有以下之一:
FirstName
和 LastName
(或两者都有,但必须有一个)
- 它必须有一个有效的
Age
(整数,介于 0 和 150 之间)
你如何属性对这个简单的案例进行基础测试?
我认为您无法以与语言无关的方式有意义地回答这个问题,因为整体设计方法将完全取决于相关语言的功能。
例如,在具有强静态类型和求和类型的语言中,上述大部分需求都可以使用类型系统以声明方式建模。这是一个 F# 示例:
type Name =
| FirstName of string
| LastName of string
| FullName of string * string
此 Name
类型只能包含名字和/或姓氏。无法创建不符合要求的值。
可以通过将类型放在单独的模块中来隐藏以下 Age 类型的 case 构造函数。如果该模块仅导出下面的 toAge
(和 getAge
)函数,则创建 Age
值的唯一方法是调用 toAge
.
type Age = Age of int
let toAge x =
if 0 <= x && x <= 150
then Some (Age x)
else None
let getAge (Age x) = x
使用这些辅助类型,您现在可以定义 Person
类型:
type Person = { Name : Name; Age : Age }
大部分需求都嵌入到类型系统中。您无法创建 Person
类型的无效值。
唯一可能失败的行为包含在 toAge
函数中,因此这是您可以有意义地接受基于 属性 的测试的唯一行为。下面是一个使用 FsCheck 的例子:
open System
open FsCheck
open FsCheck.Xunit
open Swensen.Unquote
[<Property(QuietOnSuccess = true)>]
let ``Value in valid age range can be turned into Age value`` () =
Prop.forAll (Gen.choose(0, 150) |> Arb.fromGen) (fun i ->
let actual = toAge i
test <@ actual |> Option.map getAge |> Option.exists ((=) i) @>)
[<Property(QuietOnSuccess = true)>]
let ``Value in invalid age range can't be turned into Age value`` () =
let tooLow = Gen.choose(Int32.MinValue, -1)
let tooHigh = Gen.choose(151, Int32.MaxValue)
let invalid = Gen.oneof [tooLow; tooHigh] |> Arb.fromGen
Prop.forAll invalid (fun i ->
let actual = toAge i
test <@ actual |> Option.isNone @>)
如您所知,它测试了两种情况:有效输入值和无效输入值。它通过为每种情况定义生成器,然后验证 actual
值来做到这一点。
考虑这个简单的例子:
- 有一个
Person
对象 - 它必须具有以下之一:
FirstName
和LastName
(或两者都有,但必须有一个) - 它必须有一个有效的
Age
(整数,介于 0 和 150 之间)
你如何属性对这个简单的案例进行基础测试?
我认为您无法以与语言无关的方式有意义地回答这个问题,因为整体设计方法将完全取决于相关语言的功能。
例如,在具有强静态类型和求和类型的语言中,上述大部分需求都可以使用类型系统以声明方式建模。这是一个 F# 示例:
type Name =
| FirstName of string
| LastName of string
| FullName of string * string
此 Name
类型只能包含名字和/或姓氏。无法创建不符合要求的值。
可以通过将类型放在单独的模块中来隐藏以下 Age 类型的 case 构造函数。如果该模块仅导出下面的 toAge
(和 getAge
)函数,则创建 Age
值的唯一方法是调用 toAge
.
type Age = Age of int
let toAge x =
if 0 <= x && x <= 150
then Some (Age x)
else None
let getAge (Age x) = x
使用这些辅助类型,您现在可以定义 Person
类型:
type Person = { Name : Name; Age : Age }
大部分需求都嵌入到类型系统中。您无法创建 Person
类型的无效值。
唯一可能失败的行为包含在 toAge
函数中,因此这是您可以有意义地接受基于 属性 的测试的唯一行为。下面是一个使用 FsCheck 的例子:
open System
open FsCheck
open FsCheck.Xunit
open Swensen.Unquote
[<Property(QuietOnSuccess = true)>]
let ``Value in valid age range can be turned into Age value`` () =
Prop.forAll (Gen.choose(0, 150) |> Arb.fromGen) (fun i ->
let actual = toAge i
test <@ actual |> Option.map getAge |> Option.exists ((=) i) @>)
[<Property(QuietOnSuccess = true)>]
let ``Value in invalid age range can't be turned into Age value`` () =
let tooLow = Gen.choose(Int32.MinValue, -1)
let tooHigh = Gen.choose(151, Int32.MaxValue)
let invalid = Gen.oneof [tooLow; tooHigh] |> Arb.fromGen
Prop.forAll invalid (fun i ->
let actual = toAge i
test <@ actual |> Option.isNone @>)
如您所知,它测试了两种情况:有效输入值和无效输入值。它通过为每种情况定义生成器,然后验证 actual
值来做到这一点。