函数式程序员如何测试返回单元的函数?

Sco*_*rod 12 f#

函数式程序员如何测试返回单元的函数?

就我而言,我相信我需要对这个函数的接口进行单元测试:

let logToFile (filePath:string) (formatf : 'data -> string) data =
    use file = new System.IO.StreamWriter(filePath)
    file.WriteLine(formatf data)
    data
Run Code Online (Sandbox Code Playgroud)

当我使用I/O对功能进行单元测试时,推荐的方法是什么?

在OOP中,我相信可以利用测试间谍.

Test Spy模式是否转换为函数式编程?

我的客户端看起来像这样:

[<Test>]
let ``log purchase``() =
    [OneDollarBill] |> select Pepsi
                    |> logToFile "myFile.txt" (sprintf "%A")
                    |> should equal ??? // IDK
Run Code Online (Sandbox Code Playgroud)

我的域名如下:

module Machine

type Deposit =
    | Nickel
    | Dime
    | Quarter
    | OneDollarBill
    | FiveDollarBill

type Selection =
    | Pepsi
    | Coke
    | Sprite
    | MountainDew

type Attempt = {  
    Price:decimal
    Need:decimal
}

type Transaction = {
    Purchased:Selection  
    Price:decimal
    Deposited:Deposit list
}

type RequestResult =
    | Granted of Transaction
    | Denied of Attempt

(* Functions *)
open System

let insert coin balance = coin::balance
let refund coins = coins

let priceOf = function
    | Pepsi
    | Coke
    | Sprite
    | MountainDew  -> 1.00m

let valueOf = function
    | Nickel         -> 0.05m
    | Dime           -> 0.10m
    | Quarter        -> 0.25m
    | OneDollarBill  -> 1.00m
    | FiveDollarBill -> 5.00m

let totalValue coins =
    (0.00m, coins) ||> List.fold (fun acc coin -> acc + valueOf coin)

let logToFile (filePath:string) (formatf : 'data -> string) data =
    let message = formatf data
    use file = new System.IO.StreamWriter(filePath)
    file.WriteLine(message)
    data

let select item deposited =
    if totalValue deposited >= priceOf item

    then Granted { Purchased=item
                   Deposited=deposited
                   Price = priceOf item }

    else Denied { Price=priceOf item; 
                  Need=priceOf item - totalValue deposited }
Run Code Online (Sandbox Code Playgroud)

Tom*_*cek 13

不要认为这是一个权威的答案,因为我不是测试专家,但我对这个问题的回答是,在一个完美的世界中,你不能也不需要测试 - unit返回功能.

理想情况下,您可以构建代码,使其由一些IO组成,以读取数据,转换编码所有逻辑,一些IO来保存数据:

read
|> someLogic
|> someMoreLogic
|> write
Run Code Online (Sandbox Code Playgroud)

这个想法是,你的所有重要的东西都在someLogicsomeMoreLogicreadwrite是完全微不足道的-他们读文件作为字符串或行的序列.这很简单,你不需要测试它(现在,你可以通过再次读回文件来测试实际的文件写入,但是当你想测试文件IO而不是你编写的任何逻辑时).

这是你在OO中使用mock的地方,但由于你有一个很好的功能结构,你现在可以写:

testData
|> someLogic
|> someMoreLogic
|> shouldEqual expectedResult
Run Code Online (Sandbox Code Playgroud)

现在,实际上,世界并不总是那么好,类似于spy操作的东西最终变得有用 - 也许是因为你与一个非纯粹功能的世界互操作.

Phil Trelford有一个很好的非常简单的记录器,可以让你记录对一个函数的调用,并检查它是否已被预期的输入调用 - 这是我发现有用的东西很多次(而且它很简单,你不需要一个框架).


The*_*Fox 10

显然,只要代码单元将其依赖项作为参数,就可以像在命令式代码中一样使用模拟.

但是,对于另一种方法,我发现Ken Scambler的这个谈话真的很有趣Mocks&stubs.据我所知,一般的论点是你应该避免使用模拟,尽可能保持所有函数的纯度,使它们成为数据输出.在程序的最边缘,你会有一些非常简单的函数来执行重要的副作用.这些非常简单,甚至不需要测试.

您提供的功能很简单,属于该类别.使用模拟或类似方法进行测试只需要确保调用某些方法,而不是发生副作用.这样的测试没有意义,并且不会增加代码本身的任何价值,同时仍然增加了维护负担.最好使用集成测试或端到端测试来测试副作用部分,该测试实际上会查看写入的文件.

关于这个主题的另一个好话是加里伯恩哈特的边界,它讨论了功能核心,命令性壳牌的概念.