Как вы издеваетесь над потоком вывода?
Под "выходным паром" понимается любой объект, который получает последовательность байтов, или символов или что-то еще. Итак, java.io.OutputStream, но также java.io.Writer, javax.xml.stream.XMLStreamWriter метод writeCharacters и т.д.
Я пишу mock-based тесты для класса, основной функцией которого является запись потока данных в один из них (XMLStreamWriter, как это бывает).
Проблема заключается в том, что поток данных записывается в ряд вызовов метода записи, но главное - это не вызовы, а данные. Например, учитывая XMLStreamWriter out
, они:
out.writeCharacters("Hello, ");
out.writeCharacters("world!");
Соответствует этому:
out.writeCharacters("Hello, world!");
Это действительно не имеет значения (для моих целей), что происходит. Будет определенная последовательность вызовов, но мне все равно, что это такое, поэтому я не хочу писать ожидания для этой конкретной последовательности. Я просто хочу ожидать, что какой-то поток данных будет написан любым способом.
Один из вариантов - переключиться на тестирование на основе состояния. Я мог бы накапливать данные в буфере и делать утверждения об этом. Но поскольку я пишу XML, это означало бы создание довольно сложных и уродливых утверждений. Mocking кажется гораздо лучшим способом справиться с большей проблемой написания XML.
Итак, как мне это сделать с макетом?
Я использую Moxie для насмешки, но мне интересно узнать о подходах с любой насмешливой библиотекой.
Ответы
Ответ 1
Довольно элегантная стратегия тестирования выходных или входных потоков заключается в использовании классов PipedInputStream и PipedOutputStream. Вы можете связать их вместе в настройке теста, а затем проверить, что было написано после выполнения целевого метода.
Вы можете работать в другом направлении, готовя некоторый ввод, а затем дайте тесту прочитать эти подготовленные данные и из входного потока.
В вашем случае вы можете просто высмеять эту переменную "out" с помощью PipedOutputStream и подключите к ней 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());
}
Ответ 2
Я согласен с тем, что я, вероятно, частично использовал ByteArrayOutputStream как самый низкий уровень OutputStream, получая данные после выполнения и формируя любые утверждения, которые необходимы. (возможно, используя SAX или другой синтаксический анализатор XML для чтения данных и погружения в структуру)
Если вы хотите сделать это с макетом, я признаю, что я несколько неполно относился к Mockito, и я думаю, что вы может выполнить то, что вы хотите сделать с пользовательским Answer, который, когда пользователь вызывает writeCharacters в вашем макете, просто добавит свой аргумент к буфером, а затем вы можете последовать за этим утверждениями.
Вот то, что у меня в голове (написано вручную, и не выполнялось так синтаксических проблем):)
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());
}
Ответ 3
(Отказ от ответственности: я автор Мокси.)
Я предполагаю, что вы хотите сделать это, используя логику, встроенную в mock, чтобы вызовы, которые нарушают ваше ожидание, быстро заканчиваются. Да, это возможно - но не изящно/просто в любой насмешливой библиотеке, о которой я знаю. (В общем, макетные библиотеки хороши при тестировании поведения вызовов методов в изоляции/последовательности, но плохо при тестировании более сложных взаимодействий между вызовами по жизненному циклу макета.) В этой ситуации большинство людей будут создавать буфер, поскольку другие ответы предлагайте - хотя это не так быстро, тестовый код проще реализовать/понять.
В текущей версии 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
Я воспроизвел следующий код:
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/info/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 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 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());
}
}