この簡単な例を考えてみましょう:
- オブジェが
Person
ある - 次のいずれかが必要です:
FirstName
およびLastName
(または両方、ただし一方は必須) - 有効な
Age
値 (整数、0 から 150 の間)が必要です
この単純なケースをどのようにプロパティ ベース テストしますか?
この簡単な例を考えてみましょう:
Person
あるFirstName
およびLastName
(または両方、ただし一方は必須)Age
値 (整数、0 から 150 の間)が必要ですこの単純なケースをどのようにプロパティ ベース テストしますか?
全体的な設計アプローチは問題の言語の機能に完全に依存するため、言語に依存しない方法でこの質問に有意義に答えることはできないと思います。
たとえば、強力な静的型と合計型を持つ言語では、上記の要件のほとんどは、型システムを使用して宣言的にモデル化できます。F# の例を次に示します。
type Name =
| FirstName of string
| LastName of string
| FullName of string * string
このName
タイプには、名、姓、またはその両方のみを含めることができます。要件に従わない値を作成することはできません。
次の Age 型のケース コンストラクターは、型を別のモジュールに配置することで非表示にできます。そのモジュールが以下のtoAge
(and 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 @>)
おわかりのように、有効な入力値と無効な入力値の 2 つのケースをテストします。これは、これらのケースごとにジェネレーターを定義し、actual
値を検証することによって行われます。