当我具有类型 MyType和以下代码时:
// models.fs
type internal Ticker(price:decimal, time:System.DateTime) =
member this.Price = price
member this.Time = time
// cache.fs
module internal cache
type TickerCache = {date:DateTime; ticker:Ticker}
let cache_duration = TimeSpan.FromSeconds 60.0
let cache_tickers = Dictionary<string, TickerCache>()
let isCached currency_pair =
cache_tickers.ContainsKey currency_pair && (DateTime.UtcNow - cache_tickers.Item(currency_pair).date) < cache_duration
let getTicker currency_pair =
let found, item = cache_tickers.TryGetValue currency_pair
if found && (DateTime.UtcNow - item.date) < cache_duration then Some item.ticker
else None
// Client.fs
namespace main
open Flurl.Http
type MyType =
let getTicker main, base_ =
let pair = sprintf"%s%s" main base_
let cached_ticker = cache.getTicker(pair)
if cached_ticker.IsSome then Task.FromResult(cached_ticker.Value)
else
let url = sprintf"%s/ticker/%s" apiBaseUrl pair
url.GetJsonAsync<Ticker>()
如何测试 GetJsonAsync 是否被调用(取决于 cache.getTicker 的结果)?
我通常使用Moq来检查是否调用了特定的 Setup ,但在这种情况下, Setup 是String的扩展方法,而不是我可以模拟的接口。
有关如何对此进行测试的建议?
(均匀的C#解决方案还可以,我会尝试转换为F#或使用C#编写测试)
[编辑]
为什么MyType是类型?
此代码旨在生成将由C#项目以这种方式使用的客户端:
(MyType是客户端)
var configuration = new Configuration(tickerCacheTTL, publicKey, secretKey);
IClient client = new Client(configuration);
var ticker = client.getTicker("A", "B");
type 会生成一个 class ,它更加“ .Net友好”。
此外,我不想每次都将配置传递给getTicker函数。
在真实的F#代码中,我使用:interface IClient with
公开公共方法。
[编辑2]
我可以在models.fs(Ticker,TickerCache)和cache.fs中使用 InternalsVisibleToAttribute :
[<assembly:System.Runtime.CompilerServices.InternalsVisibleTo("MyProject.UnitTests")>]
do()
(并将TickerCache类型也从专用更改为内部) 但如果没有在客户端(MyType)中进行适当的注入,我仍然无法模拟cache.getTicker。
答案 0 :(得分:3)
您可以将一个函数注入到类型中,从而允许您通过关闭可以检查的可变值来进行测试。我猜那将需要对您所做的进行最小的更改以使其可测试。
我在fsx脚本文件中整理了一个示例,希望可以证明这一点。
#r "packages/System.Net.Http/lib/net46/System.Net.Http.dll"
#r "packages/Flurl/lib/net40/Flurl.dll"
#r "packages/Flurl.Http/lib/net46/Flurl.Http.dll"
open System
open System.Net.Http
open Flurl
open Flurl.Http
open System.Threading.Tasks
type Ticker = {
At: DateTime
Value: float
}
module Ticker =
type Data(f:string->Task<Ticker>) =
let getTicker (m, b) =
let url = sprintf "%s/ticker" b
f(url)
member this.GetTicker = getTicker
// in test
let mutable isCalled = false
let spy s =
isCalled <- true
Task.FromResult({ At = DateTime.UtcNow; Value = 1.0 })
let t = Ticker.Data(spy)
let testR = t.GetTicker("whatever", "http://localhost:5000/")
// prod code
let fetchTicker (url:string) = url.GetJsonAsync<Ticker>()
let p = Ticker.Data(fetchTicker)
let result = t.GetTicker("whatever", "https://some.url/")
使用类型扩展名(https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/type-extensions)可能还有其他选择
可能值得退后一步,考虑是否可以采用其他方法。当F#提供一种更简单,功能更强大的方法时,您可能会像在C#中一样进行处理。具体来说,我无法确切说明您为什么选择使用myType
。
请参阅此处了解一些想法:https://en.wikibooks.org/wiki/F_Sharp_Programming/Caching