gpt4 book ai didi

unit-testing - 具有副作用的 F# UnitTesting 函数

转载 作者:行者123 更新时间:2023-12-03 09:25:17 25 4
gpt4 key购买 nike

我是刚开始学习 F# 的 C# 开发人员,我有一些关于单元测试的问题。假设我想要以下代码:

let input () = Console.In.ReadLine()

type MyType= {Name:string; Coordinate:Coordinate}

let readMyType =
input().Split(';')
|> fun x -> {Name=x.[1]; Coordinate = {
Longitude = float(x.[4].Replace(",","."))
Latitude =float(x.[5].Replace(",","."))
}}

如您所见,有几点需要考虑:
  • readMyType 调用 input() 有副作用。
  • readMyType 假设读取的字符串上有很多东西(包含';' 至少 6 列,有些列是 float 的 ',')

  • 我认为这样做的方法是:
  • 注入(inject) input() 函数作为参数
  • 尝试测试我们得到了什么(模式匹配?)
  • 按照说明使用 NUnit here

  • 老实说,我只是在努力寻找一个向我展示这一点的示例,以便学习 F# 中的语法和其他最佳实践。所以,如果你能告诉我这条路,那将是非常棒的。

    提前致谢。

    最佳答案

    一、您的功能并不是真正的功能。这是一个值(value)。函数和值的区别在于语法:如果你有任何参数,你就是一个函数;否则 - 你是一个值(value)。这种区别的结果在存在副作用的情况下非常重要:值在初始化期间只计算一次,然后永远不会改变,而每次调用它们时都会执行函数。

    对于您的具体示例,这意味着以下程序:

    let main _ =
    readMyType
    readMyType
    readMyType
    0

    只会要求用户输入一个,而不是三个。因为 readMyType是一个值,它在程序启动时被初始化一次,随后对它的任何引用都只是获取预先计算的值,但不会再次执行代码。

    二、 - 是的,你是对的:为了测试这个功能,你需要注入(inject) input函数作为参数:
    let readMyType (input: unit -> string) = 
    input().Split(';')
    |> fun x -> {Name=x.[1]; Coordinate = {
    Longitude = float(x.[4].Replace(",","."))
    Latitude =float(x.[5].Replace(",","."))
    }}

    然后让测试提供不同的输入并检查不同的结果:
    let [<Test>] ``Successfully parses correctly formatted string``() = 
    let input() = "foo;the_name;bar;baz;1,23;4,56"
    let result = readMyType input
    result |> should equal { Name = "the_name"; Coordinate = { Longitude = 1.23; Latitude = 4.56 } }

    let [<Test>] ``Fails when the string does not have enough parts``() =
    let input() = "foo"
    (fun () -> readMyType input) |> shouldFail

    // etc.

    将这些测试放在单独的项目中,添加对主项目的引用,然后将测试运行器添加到构建脚本中。

    更新
    从您的评论中,我得到的印象是,您不仅在寻求按原样测试功能(从您的原始问题得出),而且还在寻求有关改进功能本身的建议,以使其更加安全和可用.

    是的,最好检查函数内的错误情况,并返回适当的结果。然而,与 C# 不同的是,通常最好避免异常作为控制流机制。异常(exception)是针对特殊情况。对于您从未预料到的这种情况。这就是为什么它们是异常(exception)。但是由于您的函数的重点是解析输入,因此无效输入是它的正常条件之一。

    在 F# 中,您通常不会抛出异常,而是返回指示操作是否成功的结果。对于您的功能,以下类型似乎合适:
    type ErrorMessage = string
    type ParseResult = Success of MyType | Error of ErrorMessage

    然后相应地修改函数:
    let parseMyType (input: string) =
    let parts = input.Split [|';'|]
    if parts.Length < 6
    then
    Error "Not enough parts"
    else
    Success
    { Name = parts.[0]
    Coordinate = { Longitude = float(parts.[4].Replace(',','.')
    Latitude = float(parts.[5].Replace(',','.') }
    }

    该函数将返回 MyType包裹在 Success或包含在 Error 中的错误消息,我们可以在测试中检查这一点:
    let [<Test>] ``Successfully parses correctly formatted string``() = 
    let input() = "foo;the_name;bar;baz;1,23;4,56"
    let result = readMyType input
    result |> should equal (Success { Name = "the_name"; Coordinate = { Longitude = 1.23; Latitude = 4.56 } })

    let [<Test>] ``Fails when the string does not have enough parts``() =
    let input() = "foo"
    let result = readMyType input
    result |> should equal (Error "Not enough parts)

    请注意,即使代码现在检查字符串中的足够部分,仍然存在其他可能的错误情况:例如, parts.[4]可能不是有效数字。

    我不打算进一步扩展,因为这会使答案太长。我只想说两点:
  • 与 C# 不同,验证所有错误条件不必以 pyramid of doom 结尾。 .验证可以以线性方式很好地组合(参见下面的示例)。
  • F# 4.1 标准库已经提供了类似于 ParseResult 的类型。上面,命名为 Result<'t, 'e> .

  • 有关此方法的更多信息,请查看 this wonderful post (并且不要忘记探索其中的所有链接,尤其是视频)。

    在这里,我将为您提供一个示例,说明您的函数在完全验证所有内容后会是什么样子(请记住,尽管这仍然不是最干净的版本):
    let parseFloat (s: string) = 
    match System.Double.TryParse (s.Replace(',','.')) with
    | true, x -> Ok x
    | false, _ -> Error ("Not a number: " + s)

    let split n (s:string) =
    let parts = s.Split [|';'|]
    if parts.Length < n then Error "Not enough parts"
    else Ok parts

    let parseMyType input =
    input |> split 6 |> Result.bind (fun parts ->
    parseFloat parts.[4] |> Result.bind (fun lgt ->
    parseFloat parts.[5] |> Result.bind (fun lat ->
    Ok { Name = parts.[1]; Coordinate = { Longitude = lgt; Latitude = lat } } )))

    用法:
    > parseMyType "foo;name;bar;baz;1,23;4,56"
    val it : Result<MyType,string> = Ok {Name = "name";
    Coordinate = {Longitude = 1.23;
    Latitude = 4.56;};}

    > parseMyType "foo"
    val it : Result<MyType,string> = Error "Not enough parts"

    > parseMyType "foo;name;bar;baz;badnumber;4,56"
    val it : Result<MyType,string> = Error "Not a number: badnumber"

    关于unit-testing - 具有副作用的 F# UnitTesting 函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45400243/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com