有没有办法对在反应香蕉中创建的网络进行单元测试?假设我已经建立了一些带有一些输入事件的网络 - 是否可以验证事件已经产生了一些输出流/行为在一定数量的输入事件之后具有一些价值。这样做是否有意义?
我注意到有各种interpret*
功能,但似乎无法弄清楚如何使用它们。还有一个Model
模块看起来非常适合测试,但与实际实现有完全不同的类型。
有没有办法对在反应香蕉中创建的网络进行单元测试?假设我已经建立了一些带有一些输入事件的网络 - 是否可以验证事件已经产生了一些输出流/行为在一定数量的输入事件之后具有一些价值。这样做是否有意义?
我注意到有各种interpret*
功能,但似乎无法弄清楚如何使用它们。还有一个Model
模块看起来非常适合测试,但与实际实现有完全不同的类型。
当您说“单元测试”时,我在想象类似的东西QuickCheck
,您将许多输入注入网络并检查输出。为了做这样的事情,我们需要一个函数:
evalNetwork :: Network a b -> [a] -> IO [b]
在这个答案的结尾,我演示了一个interpret*
具有相似类型的函数的变体,用于特定类型的“网络”。
reactive-banana
关于网络类型的胡说八道这样的功能与 . 中使用的“整个网络”的实际类型不兼容reactive-banana
。对比涉及网络的实际函数的类型:
compile :: (forall t. Frameworks t => Moment t ()) -> IO EventNetwork
所以任何网络的类型都是forall t. Frameworks t => Moment t ()
. 没有类型变量;没有输入或输出。同样,该EventNetwork
类型没有参数。这告诉我们所有的输入和输出都是通过副作用处理的IO
。这也意味着实际上不可能有类似的功能
interpret? :: EventNetwork -> [a] -> IO [b]
因为会a
是b
什么?
这是设计的一个重要方面reactive-banana
。例如,它使编写与命令式 GUI 框架的绑定变得容易。的神奇之处reactive-banana
在于将所有副作用改组为,正如文档所说,“一个单一的、巨大的回调函数”。
此外,事件网络通常与 GUI 本身密切相关。考虑Arithmetic
示例,其中bInput1
和bInput2
都是使用实际输入小部件构建的,并且输出绑定到output
另一个小部件。
可以像在其他语言中一样使用“模拟”技术来构建测试工具。您可以将实际的 GUI 绑定替换为类似pipes-concurrency
. 我没有听说有人这样做。
更好的是,您可以并且应该在单独的函数中编写尽可能多的程序逻辑。如果您有两个类型为inA
and的输入inB
和一个类型为的输出out
,也许您可以编写如下函数
logic :: Event t inA -> Event t inB -> Behavior t out
这几乎是与 一起使用的正确类型interpretFrameworks
:
interpretFrameworks :: (forall t. Event t a -> Event t b) ->
[a] -> IO [[b]]
Event
您可以使用组合两个输入split
(或者更确切地说,将输入拆分为Event
所需的两个logic
)。现在你会有logic' :: Event t (Either inA inB) -> Behavior t out
.
您有点难以将输出转换Behavior
为Event
. 在 0.7 版中,changes
函数Reactive.Banana.Frameworks
有 type Frameworks t => Behavior t a -> Moment t (Event t a)
,你可以用它来解开Behavior
,尽管你必须在Moment
monad 中做。但是,在 0.8 版中,a
被包装为Future a
,其中Future
是未导出的类型。(Github re exporting 有问题Future
。)
展开的最简单方法Behavior
可能只是interpretFrameworks
用适当的类型重新实现。(请注意,它返回一个包含初始值和后续值列表的元组。)即使Future
没有导出,您也可以使用它的Functor
实例:
interpretFrameworks' :: (forall t. Event t a -> Behavior t b)
-> [a] -> IO (b, [[b]])
interpretFrameworks' f xs = do
output <- newIORef []
init <- newIORef undefined
(addHandler, runHandlers) <- newAddHandler
network <- compile $ do
e <- fromAddHandler addHandler
o <- changes $ f e
i <- initial $ f e
liftIO $ writeIORef init i
reactimate' $ (fmap . fmap) (\b -> modifyIORef output (++[b])) o
actuate network
bs <- forM xs $ \x -> do
runHandlers x
bs <- readIORef output
writeIORef output []
return bs
i <- readIORef init
return (i, bs)
这应该可以解决问题。
将此与 Gabriel Gonzalezmvc
或 Ertugrul Söylemez等其他框架进行对比netwire
。mvc
要求您将程序逻辑编写为有状态但其他方面纯的Pipe a b (State s) ()
,并且netwire
网络具有 type Wire s e m a b
;在这两种情况下,类型a
并b
公开网络的输入和输出。这使您可以轻松进行测试,但排除了reactive-banana
. 这是一个权衡。