我所说的“输出流”是指接收字节序列、字符序列或其他任何内容的任何对象。因此,有 java.io.OutputStream,还有 java.io.Writer、javax.xml.stream.XMLStreamWriter 的 writeCharacters 方法,等等。
我正在为一个类编写基于模拟的测试,该类的主要功能是将数据流写入其中一个(恰好是 XMLStreamWriter)。
问题在于,数据流是通过对 write 方法的一系列调用来写入的,但重要的不是调用,而是数据。例如,给定一个 XMLStreamWriter out
,这些:
out.writeCharacters("Hello, ");
out.writeCharacters("world!");
与此等效:
out.writeCharacters("Hello, world!");
发生哪种情况(对于我的目的)确实并不重要。将会有一些特定的调用序列,但我不在乎它是什么,所以我不想写对该特定序列的期望。我只是希望以任何方式写入特定的数据流。
一种选择是切换到基于状态的测试。我可以将数据累积在缓冲区中,并对其做出断言。但因为我正在编写 XML,所以这意味着要做出一些相当复杂且丑陋的断言。模拟似乎是处理 XML 编写这一更大问题的更好方法。
那么我该如何用模拟来做到这一点呢?
我正在使用 Moxie 进行模拟,但我有兴趣了解任何模拟的方法图书馆。
By 'output steam' i mean any object which receives a sequence of bytes, or characters or whatever. So, java.io.OutputStream, but also java.io.Writer, javax.xml.stream.XMLStreamWriter's writeCharacters method, and so on.
I'm writing mock-based tests for a class whose main function is to write a stream of data to one of these (the XMLStreamWriter, as it happens).
The problem is that the stream of data is written in a series of calls to the write method, but what matters is not the calls, but the data. For example, given an XMLStreamWriter out
, these:
out.writeCharacters("Hello, ");
out.writeCharacters("world!");
Are equivalent to this:
out.writeCharacters("Hello, world!");
It really doesn't matter (for my purposes) which happens. There will be some particular sequence of calls, but i don't care what it is, so i don't want to write expectations for that particular sequence. I just want to expect a certain stream of data to be written any which way.
One option would be to switch to state-based testing. I could accumulate the data in a buffer, and make assertions about it. But because i'm writing XML, that would mean making some fairly complex and ugly assertions. Mocking seems a much better way of dealing with the larger problem of writing XML.
So how do i do this with a mock?
I'm using Moxie for mocking, but i'm interested in hearing about approaches with any mocking library.
发布评论
评论(3)
测试输出或输入流的一个相当优雅的策略是使用 PipedInputStream 和 PipedOutputStream 类。您可以在测试设置中将它们连接在一起,然后在执行目标方法后检查写入的内容。
您可以从另一个方向准备一些输入,然后让测试也从输入流中读取准备好的数据。
在您的情况下,您可以使用 PipedOutputStream 模拟 "out" 变量,并以这种方式插入 PipedInputStream :
A fairly elegant strategy to test output or input streams is to use PipedInputStream and PipedOutputStream classes. You can wire them together in the set up of the test, and then check what has been written after the target method is executed.
You can work the other direction preparing some input and then let the test read this prepared data from the input stream as well.
In your case, you could just mock that "out" variable with a PipedOutputStream, and plug a PipedInputStream to it this way:
我承认我可能偏爱使用 ByteArrayOutputStream 作为最低级别的OutputStream,在执行后获取数据并执行所需的任何断言。 (也许使用 SAX 或其他 XML 解析器来读入数据并深入研究结构)
如果您想用模拟来做到这一点,我承认我有点偏向 Mockito,我认为您可以使用自定义 Answer 当用户在您的模拟,只需将它们的参数附加到缓冲区,然后您就可以对其进行断言。
这是我的想法(手写,尚未执行,因此语法问题是可以预料的:))
I'll admit that I'm probably partial to using a ByteArrayOutputStream as the lowest level OutputStream, fetching the data after execution and peforming whatever assertions that are needed. (perhaps using SAX or other XML parser to read in the data and dive through the structure)
If you want to do this with a mock, I'll admit I'm somewhat partial to Mockito, and I think you could accomplish what you're looking to do with a custom Answer which when the user invokes writeCharacters on your mock, would simply append their argument to a Buffer, and then you can make assertions on it afterwards.
Here's what I have in my head (hand written, and haven't executed so syntax issues are to be expected :) )
(免责声明:我是 Moxie 的作者。)
我假设您希望使用模拟中嵌入的逻辑来执行此操作,以便违反您期望的调用快速失败。是的,这是可能的 - 但在我所知道的任何模拟库中都不优雅/简单。 (一般来说,模拟库擅长测试隔离/序列中方法调用的行为,但不擅长测试模拟生命周期中调用之间更复杂的交互。)在这种情况下,大多数人会建立一个缓冲区作为其他答案建议 - 虽然它不会很快失败,但测试代码更容易实现/理解。
在 Moxie 的当前版本中,在模拟上添加自定义参数匹配行为意味着编写您自己的 Hamcrest 匹配器。 (JMock 2 和 Mockito 还允许您使用自定义 Hamcrest 匹配器;EasyMock 允许您指定扩展类似 IArgumentMatcher 接口的自定义匹配器。)
您需要一个自定义匹配器来验证传递给 writeCharacters 的字符串是否正确。形成您希望随着时间的推移传递到该方法的文本序列的下一部分,您可以在测试结束时查询以确保它收到所有预期的输入。使用 Moxie 遵循此方法的示例测试如下:
http://code.google.com/p/moxiemocks/source/browse/trunk/src/test/java/moxietests/StackOverflow6392946Test.java
我复制了以下代码:
(Disclaimer: I'm the author of Moxie.)
I assume you want to do this using logic embedded in the mock so that calls that violate your expectation fail fast. Yes, this is possible - but not elegant/simple in any mocking library I know of. (In general mock libraries are good at testing the behavior of method calls in isolation/sequence, but poor at testing more complex interactions between calls over the lifecycle of the mock.) In this situation most people would build up a buffer as the other answers suggest - while it doesn't fail fast, the test code is simpler to implement/understand.
In the current version of Moxie, adding custom parameter-matching behavior on a mock means writing your own Hamcrest matcher. (JMock 2 and Mockito also let you use custom Hamcrest matchers; EasyMock lets you specify custom matchers that extend a similar IArgumentMatcher interface.)
You'll want a custom matcher that will verify that the string passed to
writeCharacters
forms the next part of the sequence of text you expect to be passed into that method over time, and which you can query at the end of the test to make sure it's received all of the expected input. An example test following this approach using Moxie is here:http://code.google.com/p/moxiemocks/source/browse/trunk/src/test/java/moxietests/StackOverflow6392946Test.java
I've reproduced the code below: