6

有没有办法对在反应香蕉中创建的网络进行单元测试?假设我已经建立了一些带有一些输入事件的网络 - 是否可以验证事件已经产生了一些输出流/行为在一定数量的输入事件之后具有一些价值。这样做是否有意义?

我注意到有各种interpret*功能,但似乎无法弄清楚如何使用它们。还有一个Model模块看起来非常适合测试,但与实际实现有完全不同的类型。

4

1 回答 1

5

当您说“单元测试”时,我在想象类似的东西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]

因为会ab什么?

这是设计的一个重要方面reactive-banana。例如,它使编写与命令式 GUI 框架的绑定变得容易。的神奇之处reactive-banana在于将所有副作用改组为,正如文档所说,“一个单一的、巨大的回调函数”。

此外,事件网络通常与 GUI 本身密切相关。考虑Arithmetic示例,其中bInput1bInput2都是使用实际输入小部件构建的,并且输出绑定到output另一个小部件。

可以像在其他语言中一样使用“模拟”技术来构建测试工具。您可以将实际的 GUI 绑定替换为类似pipes-concurrency. 我没有听说有人这样做。

如何进行单元测试:抽象出逻辑

更好的是,您可以并且应该在单独的函数中编写尽可能多的程序逻辑。如果您有两个类型为inAand的输入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.

您有点难以将输出转换BehaviorEvent. 在 0.7 版中,changes函数Reactive.Banana.Frameworks有 type Frameworks t => Behavior t a -> Moment t (Event t a),你可以用它来解开Behavior,尽管你必须在Momentmonad 中做。但是,在 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)

这应该可以解决问题。

与其他 FRP 框架的比较

将此与 Gabriel Gonzalezmvc或 Ertugrul Söylemez等其他框架进行对比netwiremvc要求您将程序逻辑编写为有状态但其他方面纯的Pipe a b (State s) (),并且netwire网络具有 type Wire s e m a b;在这两种情况下,类型ab公开网络的输入和输出。这使您可以轻松进行测试,但排除了reactive-banana. 这是一个权衡。

于 2014-11-30T21:20:54.777 回答