基于属性的测试,用于简单的对象验证

时间:2015-09-30 13:25:38

标签: unit-testing testing language-agnostic property-based-testing

考虑这个简单的例子:

  • Theres a Person object
  • 它必须包含以下内容之一:FirstNameLastName(或两者都有,但必须有一个)
  • 必须具有有效的Age(整数,介于0和150之间)

你如何对这个简单的案例进行基地测试?

1 个答案:

答案 0 :(得分:4)

我认为你不能用语言无关的方式回答这个问题,因为整体设计方法将完全取决于所讨论语言的能力。

例如,在具有强静态类型和求和类型的语言中,可以使用类型系统以声明方式对上述大多数要求进行建模。这是一个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值来实现此目的。