java 你如何模拟输出流?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6392946/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do you mock an output stream?
提问by Tom Anderson
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.
“输出流”是指任何接收字节序列、字符或其他任何内容的对象。所以,java.io.OutputStream,还有java.io.Writer、javax.xml.stream.XMLStreamWriter的writeCharacters方法等等。
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).
我正在为一个类编写基于模拟的测试,该类的主要功能是将数据流写入其中一个(XMLStreamWriter,碰巧)。
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:
问题在于数据流是通过对 write 方法的一系列调用写入的,但重要的不是调用,而是数据。例如,给定一个 XMLStreamWriter out
,这些:
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.
一种选择是切换到基于状态的测试。我可以在缓冲区中累积数据,并对其进行断言。但是因为我正在编写 XML,这意味着要做出一些相当复杂和丑陋的断言。模拟似乎是处理编写 XML 的更大问题的更好方法。
So how do i do this with a mock?
那么我如何用模拟来做到这一点?
I'm using Moxiefor mocking, but i'm interested in hearing about approaches with any mocking library.
我正在使用Moxie进行模拟,但我有兴趣了解任何模拟库的方法。
回答by Mr.Eddart
A fairly elegant strategy to test output or input streams is to use PipedInputStreamand PipedOutputStreamclasses. 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.
测试输出或输入流的一个相当优雅的策略是使用PipedInputStream和PipedOutputStream类。您可以在测试的设置中将它们连接在一起,然后在执行目标方法后检查已写入的内容。
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:
在您的情况下,您可以使用 PipedOutputStream模拟“out”变量,并以这种方式将 PipedInputStream 插入其中:
private BufferedReader reader;
@Before
public void init() throws IOException {
PipedInputStream pipeInput = new PipedInputStream();
reader = new BufferedReader(
new InputStreamReader(pipeInput));
BufferedOutputStream out = new BufferedOutputStream(
new PipedOutputStream(pipeInput))));
//Here you will have to mock the output somehow inside your
//target object.
targetObject.setOutputStream (out);
}
@Test
public test() {
//Invoke the target method
targetObject.targetMethod();
//Check that the correct data has been written correctly in
//the output stream reading it from the plugged input stream
Assert.assertEquals("something you expects", reader.readLine());
}
回答by Charlie
I'll admit that I'm probably partial to using a ByteArrayOutputStreamas 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)
我承认我可能倾向于使用ByteArrayOutputStream作为最低级别的 OutputStream,在执行后获取数据并执行所需的任何断言。(也许使用 SAX 或其他 XML 解析器来读入数据并深入了解结构)
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 Answerwhich 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.
如果你想用模拟来做这件事,我承认我有点偏向Mockito,我认为你可以用自定义Answer来完成你想要做的事情,当用户在你的模拟上调用 writeCharacters 时,会只需将它们的参数附加到缓冲区,然后您就可以对其进行断言。
Here's what I have in my head (hand written, and haven't executed so syntax issues are to be expected :) )
这是我脑子里的东西(手写的,还没有执行,所以语法问题是意料之中的:))
public void myTest() {
final XMLStreamWriter mockWriter = Mockito.mock(XMLStreamWriter.class);
final StringBuffer buffer = new StringBuffer();
Mockito.when(mockWriter.writeCharacters(Matchers.anyString())).thenAnswer(
new Answer<Void>() {
Void answer(InvocationOnMock invocation) {
buffer.append((String)invocation.getArguments()[0]);
return null;
}
});
//... Inject the mock and do your test ...
Assert.assertEquals("Hello, world!",buffer.toString());
}
回答by pobrelkey
(Disclaimer: I'm the author of Moxie.)
(免责声明:我是 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.
我假设您想使用嵌入在模拟中的逻辑来执行此操作,以便违反您期望的调用快速失败。是的,这是可能的 - 但在我知道的任何模拟库中都不优雅/简单。(一般来说,mock 库擅长以隔离/顺序测试方法调用的行为,但不擅长在模拟的生命周期内测试调用之间更复杂的交互。)在这种情况下,大多数人会建立一个缓冲区作为其他答案建议 - 虽然它不会很快失败,但测试代码更容易实现/理解。
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.)
在当前版本的 Moxie 中,在模拟上添加自定义参数匹配行为意味着编写您自己的 Hamcrest 匹配器。(JMock 2 和 Mockito 还允许您使用自定义 Hamcrest 匹配器;EasyMock 允许您指定扩展类似 IArgumentMatcher 接口的自定义匹配器。)
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:
您需要一个自定义匹配器来验证传递给的字符串是否writeCharacters
形成了您希望随着时间的推移传递给该方法的文本序列的下一部分,并且您可以在测试结束时查询以确保它是收到所有预期的输入。使用 Moxie 遵循此方法的示例测试如下:
I've reproduced the code below:
我已经复制了下面的代码:
import moxie.Mock;
import moxie.Moxie;
import moxie.MoxieOptions;
import moxie.MoxieRule;
import moxie.MoxieUnexpectedInvocationError;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
// Written in response to... http://stackoverflow.com/questions/6392946/
public class StackOverflow6392946Test {
private static class PiecewiseStringMatcher extends BaseMatcher<String> {
private final String toMatch;
private int pos = 0;
private PiecewiseStringMatcher(String toMatch) {
this.toMatch = toMatch;
}
public boolean matches(Object item) {
String itemAsString = (item == null) ? "" : item.toString();
if (!toMatch.substring(pos).startsWith(itemAsString)) {
return false;
}
pos += itemAsString.length();
return true;
}
public void describeTo(Description description) {
description.appendText("a series of strings which when concatenated form the string \"" + toMatch + '"');
}
public boolean hasMatchedEntirely() {
return pos == toMatch.length();
}
}
@Rule
public MoxieRule moxie = new MoxieRule();
@Mock
public XMLStreamWriter xmlStreamWriter;
// xmlStreamWriter gets invoked with strings which add up to "blah blah", so the test passes.
@Test
public void happyPathTest() throws XMLStreamException{
PiecewiseStringMatcher addsUpToBlahBlah = new PiecewiseStringMatcher("blah blah");
Moxie.expect(xmlStreamWriter).anyTimes().on().writeCharacters(Moxie.argThat(addsUpToBlahBlah));
xmlStreamWriter.writeCharacters("blah ");
xmlStreamWriter.writeCharacters("blah");
Assert.assertTrue(addsUpToBlahBlah.hasMatchedEntirely());
}
// xmlStreamWriter's parameters don't add up to "blah blah", so the test would fail without the catch clause.
// Also note that the final assert is false.
@Test
public void sadPathTest1() throws XMLStreamException{
// We've specified the deprecated IGNORE_BACKGROUND_FAILURES option as otherwise Moxie works very hard
// to ensure that unexpected invocations can't get silently swallowed (so this test will fail).
Moxie.reset(xmlStreamWriter, MoxieOptions.IGNORE_BACKGROUND_FAILURES);
PiecewiseStringMatcher addsUpToBlahBlah = new PiecewiseStringMatcher("blah blah");
Moxie.expect(xmlStreamWriter).anyTimes().on().writeCharacters(Moxie.argThat(addsUpToBlahBlah));
xmlStreamWriter.writeCharacters("blah ");
try {
xmlStreamWriter.writeCharacters("boink");
Assert.fail("above line should have thrown a MoxieUnexpectedInvocationError");
} catch (MoxieUnexpectedInvocationError e) {
// as expected
}
// In a normal test we'd assert true here.
// Here we assert false to verify that the behavior we're looking for has NOT occurred.
Assert.assertFalse(addsUpToBlahBlah.hasMatchedEntirely());
}
// xmlStreamWriter's parameters add up to "blah bl", so the mock itself doesn't fail.
// However the final assertion fails, as the matcher didn't see the entire string "blah blah".
@Test
public void sadPathTest2() throws XMLStreamException{
PiecewiseStringMatcher addsUpToBlahBlah = new PiecewiseStringMatcher("blah blah");
Moxie.expect(xmlStreamWriter).anyTimes().on().writeCharacters(Moxie.argThat(addsUpToBlahBlah));
xmlStreamWriter.writeCharacters("blah ");
xmlStreamWriter.writeCharacters("bl");
// In a normal test we'd assert true here.
// Here we assert false to verify that the behavior we're looking for has NOT occurred.
Assert.assertFalse(addsUpToBlahBlah.hasMatchedEntirely());
}
}