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

时间:2016-07-19 15:20:38

标签: f#

功能程序员如何测试返回单位的函数?

在我的情况下,我相信我需要对这个函数的接口进行单元测试:

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

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

在OOP中,我相信可以利用Test Spy

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

我的客户看起来像这样:

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

我的域名如下:

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 }

2 个答案:

答案 0 :(得分:12)

不要认为这是一个权威的答案,因为我不是测试专家,但我对这个问题的回答是,在一个完美的世界里,你不能也不需要测试{{1返回函数。

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

unit

我们的想法是,所有重要内容都在read |> someLogic |> someMoreLogic |> write someLogic中,而someMoreLogicread完全无关紧要 - 他们将文件读取为字符串或行序列。这很简单,您不需要对其进行测试(现在,您可以通过再次读取文件来测试实际的文件写入,但是当您想要测试文件IO时 >而不是你写的任何逻辑。

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

write

现在,实际上,世界并不总是那么好,像testData |> someLogic |> someMoreLogic |> shouldEqual expectedResult 这样的操作最终会变得有用 - 也许是因为你正在与一个非纯粹功能的世界互操作。

Phil Trelford有一个nice and very simple Recorder,它可以让你记录对一个函数的调用并检查它是否已经被预期的输入调用 - 这是我发现有用的次数(并且它很简单,你真的不需要一个框架。)

答案 1 :(得分:9)

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

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

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

关于这个主题的另一个好话是Boundaries by Gary Bernhardt,它讨论了功能核心,命令性壳牌的概念。