Дразнить читателя с Mockito
В настоящее время я отлаживаю довольно сложный алгоритм, который исправляет ошибки в битовом потоке. BitReader
Интерфейс довольно прост, и основной метод чтения выглядит так:
/**
Reads bits from the stream.
@param length number of bits to read (<= 64)
@return read bits in the least significant bits
*/
long read(int length) throws IOException;
Цель состоит в том, чтобы проверить, BitStreamFixer
фактически исправляет поток (таким способом, который здесь слишком сложно описать). По сути, мне нужно предоставить "сломанные" входы для него и проверить, является ли его вывод настолько корректным, насколько это возможно (некоторые входы не могут быть полностью исправлены), например так:
BitStreamFixer fixer = new BitStreamFixer(input);
int word1 = fixer.readWord();
int word2 = fixer.readWord();
// possibly a loop here
assertEquals(VALID_WORD1, word1);
assertEquals(VALID_WORD2, word2);
// maybe a loop here too
Теперь BitStreamFixer
класс принимает экземпляр BitReader
, При модульном тестировании фиксатора мне явно нужен один такой экземпляр. А где взять? У меня есть два очевидных варианта: либо дать ему реальную реализацию BitReader
или высмеивать это.
Первый вариант не очень привлекателен, потому что он создает зависимость от другого объекта, который не имеет ничего общего с тестируемым классом. Более того, это не так просто, потому что существующие BitReader
Реализации читают потоки ввода формы, поэтому мне понадобится либо файл, либо каким-то образом подготовленный байтовый массив, что довольно утомительно.
Последний вариант выглядит лучше и соответствует обычному подходу модульного тестирования. Тем не менее, так как я даже не должен знать, какие аргументы приведёт исправитель read
издеваться не легко. Мне придется идти с when(bitReader.read(anyInt())).thenAnswer(...)
подход, реализующий пользовательский ответ, который создаст много бесполезной логики для ложной подачи тестируемого объекта правильными битами в кусках любого размера, который он запрашивает. Учитывая, что потоки битов, с которыми я работаю, имеют довольно сложную высокоуровневую структуру, это нелегко. И введение логики в модульных тестах также не пахнет хорошо.
Как вы думаете, есть ли другой вариант? Или, может быть, один из них может быть улучшен так, что я не замечаю?
1 ответ
Напишите, протестируйте и используйте понятный многоразовый помощник по тестированию.
В общем смысле, в модульном тестировании вы должны установить уверенность в системе, наблюдая за тем, как она успешно взаимодействует с системами, в которых вы действительно уверены. Конечно, вы также хотите, чтобы система была быстрой, детерминированной и легко читаемой / изменяемой, но, в конечном счете, она становится вторичной по отношению к утверждению, что ваша система работает.
Вы перечислили два варианта:
Используйте макет
BitReader
где у вас достаточно уверенности в прогнозировании взаимодействий вашей системы, чтобы вы могли настроить весь диалог "когда А, затем Б". Моккинг может быть довольно легким, если у вас есть небольшая поверхность API независимых методов, таких как уровень RPC, но имитация может быть очень сложной, когда у вас есть объект с состоянием и непредсказуемыми вызовами методов. Насмешка также полезна для детерминистически заглушенных недетерминированных систем, таких как внешние серверы или псевдослучайные источники, или систем, которые еще не существуют; ни один из них не подходит для вас.Потому что ваш
read
Метод может принимать самые разные параметры, каждый из которых является действительным и изменяет состояние вашей системы, поэтому, вероятно, здесь не стоит использовать насмешку. Если только порядок звонковBitStreamFixer
делает дляBitReader
является достаточно детерминированным, чтобы сделать часть своего контракта, издеватьсяBitReader
скорее всего, приведет к хрупкому тесту: тесту, который ломается при изменении реализации, даже если система полностью функциональна. Вы хотите избежать этого.Обратите внимание, что насмешка никогда не должна приводить к "сложной логике", а только к сложной настройке. Вы используете макеты, чтобы не использовать реальную логику в своих тестах.
Используйте настоящий
BitReader
Похоже, это будет больно и непрозрачно для построения. Это, пожалуй, самое реалистичное решение, особенно, если вы уже закончили писать и тестировать его.Вы беспокоитесь о "введении новых зависимостей", но если ваша реализация BitReader существует и является быстрой, детерминированной и хорошо протестированной, то вам не следует чувствовать себя хуже при ее использовании, чем при использовании реального ArrayList или ByteArrayInputStream в своем тесте. Похоже, что единственная реальная проблема здесь заключается в том, что создание байтового массива затруднит поддержание вашего теста, что является обоснованным соображением.
В комментариях, однако, реальный ответ приходит через: BitWriter
ты пропускаешь.
@Test public void shouldFixBrokenStream() {
BitReader bitReader = new StreamBitReader(BitWriter.create()
.pushBits(16, 0x8080)
.pushBits(12, 0x000) // invalid 12-bit sequence
.pushBits(16, 0x8080)
.asByteArrayInputStream());
BitStreamFixer fixer = new BitStreamFixer(bitReader);
assertEquals(0x80808080, fixer.read(32));
}
/** Of course, you could skip the BitReader yourself, and just make a new one. */
@Test public void shouldFixBrokenStream_bitReader() {
BitReader bitReader = new InMemoryBitReader();
bitReader.pushBits(16, 0x8080);
bitReader.pushBits(12, 0x000); // invalid 12-bit sequence
bitReader.pushBits(16, 0x8080);
BitStreamFixer fixer = new BitStreamFixer(bitReader);
assertEquals(0x80808080, fixer.read(32));
}
Это более читабельно, чем создание непрозрачного битового потока в автономном режиме и копирование его в ваш тест (особенно если оно хорошо прокомментировано), менее хрупкое, чем имитация, и гораздо более тестируемое, чем анонимный внутренний класс (или версия того же самого на основе ответов).). Также вероятно, что вы можете использовать такую систему в нескольких тестовых случаях и, возможно, даже в нескольких тестах.