Як ви тестуєте кодер?


9

У мене є щось подібне:

public byte[] EncodeMyObject(MyObject obj)

Я проводив тестування одиниць так:

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

EDIT: Запропоновані два способи:

1) Використання твердо кодованих очікуваних значень, як у наведеному вище прикладі.

2) Використання декодера для декодування кодованого байтового масиву та порівняння об'єктів введення / виводу.

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

Проблема методу 2 полягає в тому, що тестування кодера залежить від правильної роботи декодера. Якщо кодер / декодер порушені однаково (там же), тести можуть дати помилкові позитиви.

Це цілком може бути єдиним способом перевірити цей метод. Якщо це так, то добре. Я задаю питання, чи є якісь кращі стратегії для цього виду тестування. Я не можу розкрити внутрішню частину конкретного кодера, над яким я працюю. Я взагалі запитую, як би ви вирішили цю проблему, і я не вважаю, що внутрішні справи важливі. Припустимо, що заданий вхідний об'єкт завжди виробляє один і той же масив вихідних байтів.


4
Як myObjectпроходить шлях myObjectдо { 0x01, 0x02, 0xFF }? Чи можна розбити та перевірити алгоритм? Причина, про яку я зараз питаю, виглядає так, що у вас є тест, який доводить, що одна магічна річ створює іншу магічну річ. Ваша єдина впевненість у тому, що один вхід видає один вихід. Якщо ви можете розбити алгоритм, ви можете отримати подальшу впевненість в алгоритмі та бути менш покладаючись на магічні входи та виходи.
Ентоні Пеграм

3
@Codism Що робити, якщо кодер і декодер порушені в одному місці?
ConditionRacer

2
Тести, за визначенням, щось роблять і перевіряють, чи отримали ви очікувані результати, і це тест. Звичайно, вам потрібно буде переконатися, що ви робите достатньо таких тестів, щоб переконатися, що ви виконуєте весь свій код і охоплюєте крайові випадки та інші дивацтва.
Blrfl

1
@ Justin984, ну, тепер ми йдемо глибше. Я б не виставляв цих приватних службовців як членів API кодера, звичайно, ні. Я б видалив їх із Енкодера повністю. А точніше, Кодер делегував би десь інше, залежність . Якщо це битва між нестійким методом монстрів або купою допоміжних класів, я вибираю класи помічників щоразу. Але знову ж таки, я роблю неінформовані умовиводи до вашого коду в цей момент, тому що не можу його бачити. Але якщо ви хочете завоювати впевненість у своїх тестах, менші методи робити менше речей - це спосіб дістатися до них.
Ентоні Пеграм

1
@ Justin984 Якщо специфікація зміниться, ви зміните очікуваний вихід у своєму тесті, і він тепер не вдається. Потім ви змінюєте логіку кодера для передачі. Схоже, як TDD повинен працювати, і він вийде з ладу лише тоді, коли повинен. Я не бачу, як це робить її крихкою.
Даніель Каплан

Відповіді:


1

Ви там трохи неприємні. Якщо у вас був статичний формат, в який ви кодували, ваш перший метод був би шляхом. Якби це був лише ваш власний формат, і ніхто інший не повинен був розшифрувати, як другий метод був би шлях. Але ви насправді не вписуєтесь ні в одну з цих категорій.

Що я б робив, це спробувати розбити речі за рівнем абстракції.

Тож я б почав з чогось на рівні біту, щоб перевірити щось на кшталт

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

Отже, ідея полягає в тому, що бітрайтер знає, як виписати найпримітивніші типи полів, як ints.

Більш складні типи будуть реалізовані, використовуючи та протестуючи щось подібне:

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 тоді використовуйте так звані "тестові вектори", знайте вхідні / вихідні пари, які ви можете точно використовувати для тестування кодера і декодера
храповик урод

@ratchet freak Це повертає мене до тестування із очікуваними значеннями. Що добре, це я зараз роблю, але це трохи крихко, тому я шукав, чи є кращі способи.
ConditionRacer

1
Крім уважного читання стандарту та створення тестового випадку для кожного правила, навряд чи є спосіб уникнути того, що і кодер, і декодер містять однакову помилку. Наприклад, припустимо, що "ABC" потрібно перекласти на "xyz", але кодер цього не знає, і ваш декодер також не зрозумів би "xyz", якби він коли-небудь стикався з ним. Тестові маси ручної роботи не містять послідовності "ABC", оскільки програміст не знав цього правила, а також тест із кодуванням / декодуванням випадкових рядків був би неправильно пройдений, оскільки і кодер, і декодер ігнорують проблему.
користувач281377

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

Залежно від рамки тестування та парадигми, яку ви використовуєте, ви все одно можете використовувати для цього зразок «Впорядкувати закон для впорядкування».

[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, подивіться на геркін / огірок .


1

Ви повинні вирішити, що для вас важливо.

Чи важливо для вас, що Об'єкт пережив туди-назад, а точний формат дроту насправді не важливий? Або точний формат дроту є важливою частиною функціональності вашого кодера і декодера?

Якщо перший, то просто переконайтесь, що об’єкти пережили туди і назад. Якщо кодер і декодер зламані абсолютно взаємодоповнюючими способами, то вам насправді все одно.

Якщо остання, то вам потрібно перевірити, що формат дроту такий, як ви очікували для заданих входів. Це означає або тестування формату безпосередньо, або інше використання еталонної реалізації. Але, випробувавши основи, ви можете отримати цінність з додаткових тестів на зворотні поїздки, які слід простіше писати гучно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.