在反应香蕉中测试 [英] Testing in reactive-banana

查看:202
本文介绍了在反应香蕉中测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我注意到有各种解释* 函数,但似乎无法了解如何使用它们。还有 Model 模块,它看起来非常适合测试,但与真实实现完全不同。

解决方案

当你说单元测试的时候,我想象着像 QuickCheck 这样的东西,在这里你向网络注入一些输入并检查输出。要做到这一点,我们需要一个函数:

  evalNetwork :: Network a b  - > [a]  - > IO [b] 

在这个答案的结尾附近,我演示了一个变体<$ c对于特定类型的网络,$ c> interpret * 具有类似类型的函数。

关于<$ c的庞然大物$ c> reactive-banana 网络类型



这个函数与<$ c $中使用的整个网络的实际类型不兼容C>无功香蕉。对比涉及网络的实际函数的类型:

  compile ::(forall t。Frameworks t =>矩t()) - > IO EventNetwork 

所以任何网络的类型都是 forall t。框架t =>瞬间t()。没有类型变量;没有输入或输出。同样, EventNetwork 类型没有参数。这告诉我们所有的输入和输出都是通过 IO 中的副作用来处理的。这也意味着不能有像

 解释这样的函数吗? :: EventNetwork  - > [a]  - > IO [b] 

因为 a b 是?



这是 reactive-banana设计的一个重要方面。例如,它使编写绑定到一个命令式GUI框架变得很容易。正如文档所说, reactive-banana 的神奇之处在于将所有的副作用混合在一起,就像文档中所说的那样,一个巨大的回调函数。



此外,事件网络通常与GUI本身密切相关。考虑 算术示例,其中 bInput1 bInput2 均使用实际输入小部件,并且输出被绑定到 output ,另一个小部件。



可以构建一个测试工具像其他语言一样使用模拟技术。你可以将实际的GUI绑定替换成 pipes-concurrency 之类的绑定。我还没有听说过有人这样做过。



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



更好,您可以并且应该尽可能多地在单独的函数中编写尽可能多的程序逻辑。如果您有两个输入,分别为 inA inB ,以及一个类型输出,也许你可以编写一个函数,如

  logic :: Event t inA  - >事件t inB  - >行为t 

这几乎是适用于 interpretFrameworks

  interpretFrameworks ::(forall t。Event ta  - > Event tb) - > 
[a] - > IO [b]]

您可以将两个输入 Event <使用 split (或者说,将输入分成两个 Event C $ C>逻辑)。现在你有 logic':: Event t(inA inB) - >行为t out



您将输出 Behavior 转换为一个事件。在0.7版中,改变函数 Reactive.Banana.Frameworks 的类型为 Frameworks t = >行为t a - > Moment t(Event ta),你可以用它来解开 Behavior ,尽管你必须在 Moment monad。然而,在0.8版中, a 被封装为 Future a ,其中 Future 是未导出的类型。 (有关问题Github上重新导出未来

解开 Behavior 的最简单方法可能就是重新实现 interpretFrameworks 与适当的类型。 (注意,它返回一个包含初始值和后续值列表的元组)。即使未来未被导出,您可以使用它的 Functor instance:

  interpretFrameworks'::(forall t。Event ta  - > Behavior tb)
- > [a] - > IO(b,[[b]])
interpretFrameworks'f xs = do
输出< - newIORef []
init< - newIORef undefined
(addHandler,runHandlers) < - newAddHandler
network< - compile $ do
e< - fromAddHandler addHandler
o< - 变化$ fe
i< - 初始$ fe
liftIO $ writeIORef init
reactimate'$(fmap。fmap)(\ b - > modifyIORef output(++ [b]))

启动网络
bs< - forM xs $ \x - >
runHandlers x
bs< - readIORef输出
writeIORef输出[]
返回bs
i< - readIORef init
return(i,bs)

这应该会有用。

与其他FRP框架比较



与其他框架如Gabriel Gonzalez的 mvc 或ErtugrulSöylemez的 netwire mvc 要求你编写你的程序逻辑作为有状态但纯粹的 Pipe ab(State s)(),和 netwire 网络的类型为 Wire semab ;在这两种情况下,类型 a b 都会显示网络的输入和输出。这可以让你轻松测试,但是排除了 reactive-banana 中的inlineGUI绑定。这是一个折衷。


Is there a way to unit test networks created in reactive banana? Say I've built up some network with some input events - is it possible to verify that events have produced some output stream/behaviours have some value after some number of input events. Does it even make sense to do this?

I noticed there are various interpret* functions but couldn't seem to work out how to use them. There's also the Model module which looks ideal for testing but has completely different types to the real implementation.

解决方案

When you say "unit test," I'm imagining something like QuickCheck, where you inject a number of inputs into the network and examine the outputs. To do such a thing, we'd need a function along the lines of:

evalNetwork :: Network a b -> [a] -> IO [b]

Near the end of this answer I demonstrate a variant on one of the interpret* functions that has a similar type, for a specific type of "network."

Mumbo-jumbo about reactive-banana network types

Such a function is incompatible with the actual type of "entire networks" used in reactive-banana. Contrast the type of the actual function involving networks:

compile :: (forall t. Frameworks t => Moment t ()) -> IO EventNetwork

So the type of any network is forall t. Frameworks t => Moment t (). There are no type variables; no inputs or outputs. Similarly, the EventNetwork type has no parameters. That tells us that all of the input and output is handled via side-effects in IO. It also means there can't really be a function like

interpret? :: EventNetwork -> [a] -> IO [b]

because what would a and b be?

This is an important aspect of the design of reactive-banana. It makes writing bindings to an imperative GUI framework easy, for instance. The magic of reactive-banana is to shuffle around all the side-effects into, as the docs call it, "a single, huge callback function."

Furthermore, it's typical for the event network to be intimately associated with the GUI itself. Consider the Arithmetic example, where bInput1 and bInput2 are both built using the actual input widgets, and the output is bound to output, another widget.

It would be possible to build a testing harness using "mocking" techniques as in other languages. You could substitute out the actual GUI bindings with bindings to something like pipes-concurrency. I haven't heard of anybody doing so.

How to unit test: Abstract out the logic

Better, you can and should write as much of your program logic as possible in separate functions. If you have two inputs, of types inA and inB, and one output of type out, perhaps you can write a function like

logic :: Event t inA -> Event t inB -> Behavior t out

This is almost the right type to use with interpretFrameworks:

interpretFrameworks :: (forall t. Event t a -> Event t b) ->
                       [a] -> IO [[b]]

You can combine the two input Events using split (or rather, split the input into the two Events required by logic). Now you'd have logic' :: Event t (Either inA inB) -> Behavior t out.

You're kind of stymied converting the output Behavior to an Event. In version 0.7, the changes function in Reactive.Banana.Frameworks had type Frameworks t => Behavior t a -> Moment t (Event t a), which you could have used to unwrap the Behavior, although you'd have to do it in the Moment monad. In version 0.8, however, the a is wrapped up as a Future a, where Future is an unexported type. (There's an issue on Github re exporting Future.)

The easiest way to unwrap the Behavior is probably just to reimplement interpretFrameworks with the appropriate type. (Note that it returns a tuple containing the initial value and the list of subsequent values.) Even though Future is not exported, you can use its Functor instance:

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)

This should do the trick.

Comparison with other FRP frameworks

Contrast this with other frameworks like Gabriel Gonzalez's mvc or Ertugrul Söylemez's netwire. mvc requires you to write your program logic as a stateful but otherwise-pure Pipe a b (State s) (), and netwire networks have type Wire s e m a b; in both cases the types a and b expose the input to and output from your network. This gets you easy testing, but precludes the "inline" GUI bindings available with reactive-banana. It's a tradeoff.

这篇关于在反应香蕉中测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆