Как вы издеваетесь над выходным потоком?

Под «выходным потоком» я подразумеваю любой объект, который получает последовательность байтов, символов или чего-то еще. Итак, java.io.OutputStream, а также java.io.Writer, метод writeCharacters javax.xml.stream.XMLStreamWriter и так далее.

Я пишу фиктивные тесты для класса, основной функцией которого является запись потока данных в один из них (например, в XMLStreamWriter).

Проблема в том, что поток данных записывается последовательностью вызовов метода записи, но важны не вызовы, а данные. Например, для XMLStreamWriter out это:

out.writeCharacters("Hello, ");
out.writeCharacters("world!");

эквивалентны этому:

out.writeCharacters("Hello, world!");

На самом деле не имеет значения (для моих целей), что происходит. Будет определенная последовательность вызовов, но мне все равно, что это такое, поэтому я не хочу писать ожидания для этой конкретной последовательности. Я просто хочу ожидать, что определенный поток данных будет записан каким-либо образом.

Одним из вариантов может быть переход на тестирование по состоянию. Я мог бы накапливать данные в буфере и делать по ним утверждения. Но поскольку я пишу XML, это означало бы создание довольно сложных и уродливых утверждений. Насмешки кажутся гораздо лучшим способом решения более крупной проблемы написания XML.

Итак, как мне сделать это с макетом?

Я использую Moxie для насмешек, но мне интересно узнать о подходах с любыми насмешками библиотека.


person Tom Anderson    schedule 18.06.2011    source источник


Ответы (3)


Довольно элегантная стратегия тестирования выходных или входных потоков заключается в использовании классов 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());
    }
person Mr.Eddart    schedule 27.06.2011
comment
Как подход к тестированию потоков, да, это хорошо (лучше, чем писать в ByteArrayOutputStream и делать утверждения об этом, я думаю). Но (а) в данном конкретном случае out — это XMLStreamWriter, а не поток, и (б) это не насмешка, и я думаю, что с насмешкой не будет хорошо работать. - person Tom Anderson; 27.06.2011
comment
Это прекрасно работает, однако в моем случае я должен закрыть буферизованный выходной поток, прежде чем вы сможете выполнить readLine() на считывателе. Мой код просто зависает, если я этого не сделаю. - person Jason Slobotski; 05.09.2018

Я признаю, что я, вероятно, неравнодушен к использованию ByteArrayOutputStream как OutputStream самого низкого уровня, извлекая данные после выполнения и выполняя любые необходимые утверждения. (возможно, используя SAX или другой парсер XML для чтения данных и погружения в структуру)

Если вы хотите сделать это с макетом, я признаю, что несколько неравнодушен к Mockito, и я думаю, что вы может выполнить то, что вы хотите сделать, с помощью пользовательского Ответ, который, когда пользователь вызывает 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());
}    
person Charlie    schedule 18.06.2011

(Отказ от ответственности: я автор 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

Я воспроизвел код ниже:

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());
    }
}
person pobrelkey    schedule 25.10.2013