Как вы издеваетесь над выходным потоком? - PullRequest
14 голосов
/ 18 июня 2011

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

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

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

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

эквивалентны следующему:

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

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

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

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

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

Ответы [ 3 ]

16 голосов
/ 27 июня 2011

Довольно элегантная стратегия для тестирования выходных или входных потоков заключается в использовании 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());
    }
4 голосов
/ 18 июня 2011

Я признаю, что, вероятно, неравнодушен к использованию ByteArrayOutputStream в качестве выходного потока самого низкого уровня, получая данные после выполнения и выполняя любые необходимые утверждения.(возможно, используя 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());
}    
1 голос
/ 25 октября 2013

(Отказ от ответственности: я автор 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... /8102965/kak-vy-izdevaetes-nad-vyhodnym-potokom
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());
    }
}
...