Как вы тестируете кодер?

9

У меня есть что-то вроде этого:

public byte[] EncodeMyObject(MyObject obj)

Я был модульное тестирование, как это:

byte[] expectedResults = new byte[3]{ 0x01, 0x02, 0xFF };
Assert.IsEqual(expectedResults, EncodeMyObject(myObject));

РЕДАКТИРОВАТЬ: Два способа, которые я видел предложены:

1) Использование ожидаемых значений в жестком коде, как в примере выше.

2) Использование декодера для декодирования закодированного байтового массива и сравнения объектов ввода / вывода.

Проблема, которую я вижу со способом 1, состоит в том, что он очень хрупок и требует много жестко закодированных значений.

Проблема метода 2 заключается в том, что тестирование кодера зависит от правильной работы декодера. Если кодер / декодер сломаны одинаково (в одном и том же месте), то тесты могут дать ложные срабатывания.

Вполне возможно, что это единственный способ проверить этот тип метода. Если это так, то хорошо. Я задаю вопрос, чтобы увидеть, есть ли какие-либо лучшие стратегии для этого типа тестирования. Я не могу раскрыть внутренности конкретного кодера, над которым я работаю. В общем, я спрашиваю, как бы вы решили эту проблему, и я не чувствую, что внутреннее устройство важно. Предположим, что данный входной объект всегда будет производить один и тот же выходной байтовый массив.

ConditionRacer
источник
4
Как myObjectидет от myObjectк { 0x01, 0x02, 0xFF }? Можно ли этот алгоритм сломать и проверить? Причина, по которой я спрашиваю, в настоящее время, похоже, у вас есть тест, который доказывает, что одна магическая вещь производит другую магическую вещь. Ваша единственная уверенность в том, что один вход производит один выход. Если вы можете сломать алгоритм, вы можете получить дополнительную уверенность в алгоритме и меньше полагаться на волшебные входы и выходы.
Энтони Пеграм,
3
@Codism Что если кодер и декодер сломаны в одном месте?
ConditionRacer
2
Тесты по определению делают что-то и проверяют, получили ли вы ожидаемые результаты, что и делает ваш тест. Вам, конечно, нужно убедиться, что вы выполняете достаточно тестов, как этот, чтобы убедиться, что вы выполняете весь свой код и покрываете крайние случаи и другие странности.
Blrfl
1
@ Джастин984, ну, теперь мы углубимся. Я не стал бы выставлять эти частные внутренние элементы как члены API энкодера, конечно, нет. Я бы полностью удалил их из энкодера. Вернее, кодировщик делегировал бы кому-то еще зависимость . Если это битва между непроверенным методом монстров или группой вспомогательных классов, я выбираю вспомогательные классы каждый раз. Но опять же, на этом этапе я делаю неосведомленные выводы для вашего кода, потому что не вижу его. Но если вы хотите обрести уверенность в своих тестах, меньшие методы, выполняющие меньше задач, - это способ достичь этого.
Энтони Пеграм
1
@ Justin984 Если спецификация меняется, вы меняете ожидаемый результат в своем тесте, и теперь он терпит неудачу. Затем вы изменяете логику кодера для прохождения. Кажется, как именно TDD должен работать, и он потерпит неудачу только тогда, когда должен. Я не понимаю, как это делает его ломким.
Даниэль Каплан

Ответы:

1

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

Что я бы сделал, так это попытался бы разбить вещи по уровню абстракции.

Итак, я бы начал с чего-то на уровне битов, чтобы я протестировал что-то вроде

bitWriter = new BitWriter();
bitWriter.writeInt(42, bits = 7);
assertEqual( bitWriter.data(), {0x42} )

Таким образом, идея состоит в том, что разработчик битов знает, как записывать наиболее примитивные типы полей, например, целые.

Более сложные типы будут реализованы с использованием и тестированием чего-то вроде:

bitWriter = new BitWriter();
writeDate(bitWriter, new Datetime(2001, 10, 4));

bitWriter2 = new BitWriter();
bitWriter2.writeInt(2001, 12)
bitWriter2.writeInt(10, 4)
bitWriter2.writeInt(4, 6)

assertEquals(bitWriter.data(), bitWriter2.data() )

Обратите внимание, что это позволяет избежать каких-либо знаний о том, как упаковываются реальные биты. Это было проверено предыдущим тестом, и для этого теста мы почти предположим, что он работает.

Тогда на следующем уровне абстракции у нас будет

bitWriter = new BitWriter();
encodeObject(bitWriter, myObject);


bitWriter2 = new BitWriter();
bitWriter2.writeInt(42, 32)
writeDate(bitWriter2, new Datetime(2001, 10, 4));
writeVarString(bitWriter2, "alphanumeric");

assertEquals(bitWriter.data(), bitWriter2.data() )

поэтому, опять же, мы не пытаемся включить знания о том, как на самом деле кодируются строки переменных, даты или числа. В этом тесте нас интересует только кодировка, создаваемая encodeObject.

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

Уинстон Эверт
источник
Мне это нравится. Я думаю, это то, что некоторые другие комментаторы говорили о том, чтобы разбить его на более мелкие кусочки. Это не позволяет полностью избежать проблемы при изменении спецификации, но делает ее лучше.
ConditionRacer
6

Зависит. Если кодировка является чем-то полностью фиксированным, где каждая реализация должна создавать абсолютно одинаковые выходные данные, не имеет смысла проверять что-либо, кроме проверки того, что пример входных данных соответствует точно ожидаемым выходным данным. Это самый очевидный тест, и, вероятно, самый простой для написания.

Если имеется пространство для маневрирования с альтернативными выходами, как в стандарте MPEG (например, есть определенные операторы, которые вы можете применить к входу, но вы можете компенсировать усилия по кодированию в зависимости от качества вывода или места для хранения), тогда лучше применить определил стратегию декодирования для выходных данных и убедитесь, что они совпадают с входными данными - или, если кодирование с потерями, достаточно разумно близки к исходным входным данным. Это сложнее в программировании, но защищает вас от любых будущих улучшений, которые могут быть внесены в ваш кодировщик.

Килиан Фот
источник
2
Предположим, вы используете декодер и сравниваете значения. Что если кодер и декодер оба сломаны в одном и том же месте? Кодировщик кодирует неправильно, и декодер декодирует неправильно, но объекты ввода / вывода правильны, потому что процесс был выполнен неправильно дважды.
ConditionRacer
@ Justin984 затем используйте так называемые «тестовые векторы», знайте пары ввода / вывода, которые вы можете точно использовать для тестирования кодера и декодера
трещотка, урод
@ rachet freak Это возвращает меня к тестированию с ожидаемыми значениями. Что хорошо, это то, что я сейчас делаю, но это немного ломко, поэтому я искал, чтобы найти лучшие способы.
ConditionRacer
1
Помимо тщательного прочтения стандарта и создания контрольного примера для каждого правила, вряд ли есть способ избежать того, чтобы и кодер, и декодер содержали одну и ту же ошибку. Например, предположим, что «ABC» должен быть переведен в «xyz», но кодировщик этого не знает, и ваш декодер также не поймет «xyz», если он когда-либо столкнется с ним. Тестовые случаи ручной работы не содержат последовательность «ABC», потому что программист не знал об этом правиле, а также тест с кодированием / декодированием случайных строк мог бы пройти неправильно, потому что кодер и декодер игнорировали проблему.
user281377
1
Чтобы помочь выявить ошибки, затрагивающие как декодеры, так и кодировщики, написанные вами из-за недостатка знаний, постарайтесь получить выходные данные кодировщиков от других поставщиков; а также попробуйте проверить выход вашего кодера на сторонних декодерах. Альтернативы этому нет.
rwong
3

Проверьте это encode(decode(coded_value)) == coded_valueи decode(encode(value)) == value. Вы можете дать произвольный вход для тестов, если хотите.

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

Если у вас есть доступ к другой реализации, которая, как известно, работает, вы можете, по крайней мере, использовать ее, чтобы получить уверенность в том, что ваша реализация хороша, даже если использование ее в модульных тестах было бы невозможно.

Майкл Шоу
источник
Я согласен, что дополнительная ошибка кодера / декодера в общем случае маловероятна. В моем конкретном случае код для классов кодера / декодера генерируется другим инструментом на основе правил из базы данных. Поэтому дополняющие ошибки случаются время от времени.
ConditionRacer
Как могут быть «дополнительные ошибки»? Это подразумевает наличие внешней спецификации для закодированной формы и, следовательно, внешнего декодера.
Кевин Клайн
Я не понимаю, как вы используете слово «внешний». Но есть спецификация для того, как данные кодируются, а также декодер. Дополнительная ошибка - это когда кодер и декодер работают дополняющим, но отклоняющимся от спецификации способом. У меня есть пример в комментариях под оригинальным вопросом.
ConditionRacer
Если кодер должен был реализовать ROT13, но случайно сделал ROT14, а декодер тоже, то декодируйте (encode ('a')) == 'a', но кодер все еще не работает. Для вещей намного более сложных, чем это, вероятно, гораздо менее вероятно, что такого рода вещи произойдут, но теоретически это могло бы произойти.
Майкл Шоу
@MichaelShaw просто мелочи, кодер и декодер для ROT13 одинаковы; ROT13 - это свое собственное обратное. Если бы вы внедрили ROT14 по ошибке, то decode(encode(char))не были бы равны char(это будет равно char+2).
Том Мартенал
2

Тест на требования .

Если требования только «кодируют в поток байтов, который при декодировании создает эквивалентный объект», тогда просто протестируйте кодировщик путем декодирования. Если вы пишете кодер и декодер, просто протестируйте их вместе. У них не может быть "совпадающих ошибок". Если они работают вместе, то испытание проходит успешно.

Если существуют другие требования к потоку данных, вам придется проверить их, изучив закодированные данные.

Если кодированный формат задан заранее, то либо вам придется сверять закодированные данные с ожидаемым результатом, как вы, либо (что лучше) получить эталонный декодер, которому можно доверять для выполнения проверки. Использование эталонного декодера исключает возможность неправильного толкования спецификации формата.

Кевин Клайн
источник
1

В зависимости от используемой среды тестирования и используемой парадигмы, вы все равно можете использовать шаблон Arrange Act Assert для этого, как вы сказали.

[TestMethod]
public void EncodeMyObject_ForValidInputs_Encodes()
{
    //Arrange object under test
    MyEncoder encoderUnderTest = new MyEncoder();
    MyObject validObject = new MyOjbect();
    //arrange object for condition under test

    //Act
    byte[] actual = encoderUnderTest.EncodeMyObject(myObject);

    //Assert
    byte[] expected = new byte[3]{ 0x01, 0x02, 0xFF };
    Assert.IsEqual(expected, actual);
}

Вы должны знать требования для EncodeMyObject()и можете использовать этот шаблон для проверки каждого из них на предмет допустимых и недействительных критериев, упорядочивая каждый из них и жестко кодируя ожидаемый результат expected, аналогично для декодера.

Поскольку ожидаемые жестко запрограммированы, они будут хрупкими, если у вас есть серьезные изменения.

Вы можете быть в состоянии автоматизировать с помощью чего-то, управляемого параметром (взгляните на Pex ), или, если вы делаете DDD или BDD, посмотрите на gerkin / cucumber .

StuperUser
источник
1

Вы сами решаете, что для вас важно.

Для вас важно, что Объект выживает в обоих направлениях, и точный формат проводов на самом деле не важен? Или точный формат проводов является важной частью функциональности вашего кодера и декодера?

Если первое, то просто убедитесь, что объекты выживают в оба конца. Если кодер и декодер оба сломаны совершенно взаимодополняющими способами, вам все равно.

Если последнее, то вам нужно проверить, что формат проводов соответствует ожидаемым для заданных входов. Это означает либо тестирование формата напрямую, либо использование эталонной реализации. Но, проверив основы, вы можете получить пользу от дополнительных тестов туда-обратно, которые должны быть легче писать в объеме.

Билл Мичелл
источник