如何使用 xUnit 测试 F# 选项类型
How to test F# option types with xUnit
我想在 F# 中使用 xUnit 对这段代码进行单元测试。如何处理选项?
摘自 Scott Wlaschin 的书:领域建模功能化
type UnitQuantity = private UnitQuantity of int
// ^ private constructor
// define a module with the same name as the type
module UnitQuantity =
/// Define a "smart constructor" for UnitQuantity
/// int -> Result<UnitQuantity,string>
let create qty =
if qty < 1 then
// failure
Error "UnitQuantity can not be negative"
else if qty > 1000 then
// failure
Error "UnitQuantity can not be more than 1000"
else
// success -- construct the return value
Ok (UnitQuantity qty)
测试:
let ``Check UnitQuantity.create is one`` () =
// ARRANGE
let expected = 1
// ACT
//let unitQtyResult = UnitQuantity.create 1
match UnitQuantity.create 1 with
| Error msg -> 0
//printfn "Failure, Message is %s" msg
| Ok x -> 0
// let innerValue = UnitQuantity.value actual
// ASSERT
//Assert.Equal(expected,actual)
我知道 ACT
完全错了,这就是我被挂断的地方。我对 F# 选项、xUnit.net 和单元测试的理解都不够,无法断言函数的实际值。
单元测试的一般经验法则是 Act 部分应该是单个语句。
我们要检查的所有结果都是某种形式的断言
所以我们要断言结果是 Ok<UnitQuantity>
还是 Error<string>
。
这是模式匹配允许我们非常简洁地测试它的地方
let ``Check UnitQuantity.create is one`` () =
// ARRANGE
let qty = 1 // The quantity we supply is one
let expected = qty // We expect to get that value back
// ACT
let actual = UnitQuantity.create qty
// ASSERT
// Check if we have an Ok or an Error
match actual with
| Ok unitQuantity ->
// If Ok, check the value is what we expect
let innerValue = UnitQuantity.value unitQuantity
Assert.Equal(innerValue, expected)
// If Error raise an AssertException to fail the test
| Error errorMessage ->
let error = sprintf "Expected Ok, was Error(%s)." errorMessage
Assert.True(false, error) // Force an assertion failure with our error message
注意 UnitQuantity.value
方法,这是一个简单的解包函数,您可以将其添加到 UnitQuantity
模块的末尾,它将返回 int
值,这样您就可以轻松地比较一下
let value (UnitQuantity e) = e
如果你想测试一个 option
类型,它会非常相似,使用像这样的匹配语句
match actual with
| Some value ->
// Do your assertions here
()
| None ->
// Do your assertions here
()
我可能会直接比较结果而不是模式匹配。但是,由于 private
构造函数,您无法为 Result<UnitQuantity, string>
创建 Ok
结果。
您可以使用内置的 Result.map
映射结果的 Ok
值。使用 UnitQuantity.value
可以从 Result<UnitQuantity, string>
映射到 Result<int, string>
。所以这应该有效:
let expected = Ok 1
let actual = UnitQuantity.create 1 |> Result.map UnitQuantity.value
Assert.Equal(expected, actual)
面对类似的问题,我决定我不太关心检查值本身,而是检查高于和低于阈值的行为。我决定验证结果是否为 Ok
如果值在范围内并且 Error
超出范围。
type UnitQuantity = private UnitQuantity of int
module UnitQuantity =
let min = 1
let max = 1000
let create qty =
if qty < min then
Error $"UnitQuantity cannot be less than {min}"
else if qty > max then
Error $"UnitQuantity cannot be more than {max}"
else
Ok (UnitQuantity qty)
module Tests =
let min = 1
let max = 1000
let shouldBeError = function
| Error _ -> ()
| _ -> failwith "is not error"
let shouldBeOk = function
| Ok _ -> ()
| _ -> failwith "is not Ok"
[<Fact>]
let ``create unit qty at min`` () =
UnitQuantity.create min |> shouldBeOk
[<Fact>]
let ``create unit qty below min`` () =
UnitQuantity.create (min - 1) |> shouldBeError
[<Fact>]
let ``create unit qty at max`` () =
UnitQuantity.create max |> shouldBeOk
[<Fact>]
let ``create unit qty above max`` () =
UnitQuantity.create (max + 1) |> shouldBeError
我想在 F# 中使用 xUnit 对这段代码进行单元测试。如何处理选项?
摘自 Scott Wlaschin 的书:领域建模功能化
type UnitQuantity = private UnitQuantity of int
// ^ private constructor
// define a module with the same name as the type
module UnitQuantity =
/// Define a "smart constructor" for UnitQuantity
/// int -> Result<UnitQuantity,string>
let create qty =
if qty < 1 then
// failure
Error "UnitQuantity can not be negative"
else if qty > 1000 then
// failure
Error "UnitQuantity can not be more than 1000"
else
// success -- construct the return value
Ok (UnitQuantity qty)
测试:
let ``Check UnitQuantity.create is one`` () =
// ARRANGE
let expected = 1
// ACT
//let unitQtyResult = UnitQuantity.create 1
match UnitQuantity.create 1 with
| Error msg -> 0
//printfn "Failure, Message is %s" msg
| Ok x -> 0
// let innerValue = UnitQuantity.value actual
// ASSERT
//Assert.Equal(expected,actual)
我知道 ACT
完全错了,这就是我被挂断的地方。我对 F# 选项、xUnit.net 和单元测试的理解都不够,无法断言函数的实际值。
单元测试的一般经验法则是 Act 部分应该是单个语句。
我们要检查的所有结果都是某种形式的断言
所以我们要断言结果是 Ok<UnitQuantity>
还是 Error<string>
。
这是模式匹配允许我们非常简洁地测试它的地方
let ``Check UnitQuantity.create is one`` () =
// ARRANGE
let qty = 1 // The quantity we supply is one
let expected = qty // We expect to get that value back
// ACT
let actual = UnitQuantity.create qty
// ASSERT
// Check if we have an Ok or an Error
match actual with
| Ok unitQuantity ->
// If Ok, check the value is what we expect
let innerValue = UnitQuantity.value unitQuantity
Assert.Equal(innerValue, expected)
// If Error raise an AssertException to fail the test
| Error errorMessage ->
let error = sprintf "Expected Ok, was Error(%s)." errorMessage
Assert.True(false, error) // Force an assertion failure with our error message
注意 UnitQuantity.value
方法,这是一个简单的解包函数,您可以将其添加到 UnitQuantity
模块的末尾,它将返回 int
值,这样您就可以轻松地比较一下
let value (UnitQuantity e) = e
如果你想测试一个 option
类型,它会非常相似,使用像这样的匹配语句
match actual with
| Some value ->
// Do your assertions here
()
| None ->
// Do your assertions here
()
我可能会直接比较结果而不是模式匹配。但是,由于 private
构造函数,您无法为 Result<UnitQuantity, string>
创建 Ok
结果。
您可以使用内置的 Result.map
映射结果的 Ok
值。使用 UnitQuantity.value
可以从 Result<UnitQuantity, string>
映射到 Result<int, string>
。所以这应该有效:
let expected = Ok 1
let actual = UnitQuantity.create 1 |> Result.map UnitQuantity.value
Assert.Equal(expected, actual)
面对类似的问题,我决定我不太关心检查值本身,而是检查高于和低于阈值的行为。我决定验证结果是否为 Ok
如果值在范围内并且 Error
超出范围。
type UnitQuantity = private UnitQuantity of int
module UnitQuantity =
let min = 1
let max = 1000
let create qty =
if qty < min then
Error $"UnitQuantity cannot be less than {min}"
else if qty > max then
Error $"UnitQuantity cannot be more than {max}"
else
Ok (UnitQuantity qty)
module Tests =
let min = 1
let max = 1000
let shouldBeError = function
| Error _ -> ()
| _ -> failwith "is not error"
let shouldBeOk = function
| Ok _ -> ()
| _ -> failwith "is not Ok"
[<Fact>]
let ``create unit qty at min`` () =
UnitQuantity.create min |> shouldBeOk
[<Fact>]
let ``create unit qty below min`` () =
UnitQuantity.create (min - 1) |> shouldBeError
[<Fact>]
let ``create unit qty at max`` () =
UnitQuantity.create max |> shouldBeOk
[<Fact>]
let ``create unit qty above max`` () =
UnitQuantity.create (max + 1) |> shouldBeError