Як перевірити, коли впорядкування даних є занадто громіздким?


19

Я пишу парсер і як частина цього у мене є Expanderклас, який "розширює" єдине складне висловлювання на кілька простих операторів. Наприклад, це розширить це:

x = 2 + 3 * a

в:

tmp1 = 3 * a
x = 2 + tmp1

Зараз я думаю про те, як протестувати цей клас, а саме про те, як упорядкувати тести. Я міг створити дерево введення синтаксису вручну:

var input = new AssignStatement(
    new Variable("x"),
    new BinaryExpression(
        new Constant(2),
        BinaryOperator.Plus,
        new BinaryExpression(new Constant(3), BinaryOperator.Multiply, new Variable("a"))));

Або я можу записати його як рядок і проаналізувати його:

var input = new Parser().ParseStatement("x = 2 + 3 * a");

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

Моє запитання: чи добре покластися (або повністю) на такі інтеграційні тести для тестування цього Expanderкласу?


3
Те, що помилка Parserможе вийти з ладу на якомусь іншому тесті, не є проблемою, якщо ви звично здійснюєте лише при нульових збоях, навпаки, це означає, що ви маєте більше покриття Parser. Я б хотів би турбуватися про те, що помилка Parserможе зробити цей тест успішним, коли він мав би не вдатися . Зрештою, є тести, щоб знайти помилки - тест порушується, коли він не має, але повинен мати.
Йонас Кьолкер

Відповіді:


27

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

var input = new Parser().ParseStatement("x = 2 + 3 * a");

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

Розробники іноді надмірно зосередитися на чистоті своїх модульних тестів , або розробка модульних тестів і модульних тестів тільки , без якого - або модуля, інтеграції, стресу або інших видів випробувань. Усі ці форми є дійсними та корисними, і вони несуть належну відповідальність розробників - не лише питання Q / A або оперативний персонал далі вниз.

Один із підходів, який я використав, - це почати з цих прогонів вищого рівня, а потім використовувати дані, отримані з них, для побудови тесту довготермінового найнижчого загального знаменника. Наприклад, коли ви скидаєте структуру даних із inputнаведеного вище, ви можете легко сконструювати:

var input = new AssignStatement(
    new Variable("x"),
    new BinaryExpression(
        new Constant(2),
        BinaryOperator.Plus,
        new BinaryExpression(new Constant(3), BinaryOperator.Multiply, new Variable("a"))));

вид тесту, який тестує на самому низькому рівні. Таким чином ви отримуєте приємну суміш: жменю самих основних, примітивних тестів (чисті одиничні тести), але не витратили тиждень на написання тестів на цьому примітивному рівні. Це дає вам ресурс часу, необхідний для написання багатьох інших, трохи менше атомних тестів, використовуючи Parserяк помічник. Кінцевий результат: більше тестів, більше висвітлення, більше кутових та інших цікавих випадків, кращий код та більш висока якість.


2
Це розумно - особливо щодо того, що все залежить від багатьох інших. Хороший одиничний тест повинен перевірити мінімально можливий. Все, що знаходиться в межах мінімально можливої ​​кількості, повинно бути перевірено попереднім одиничним тестом. Якщо ви повністю перевірили Parser, ви можете припустити, що можете сміливо використовувати Parser для тестування ParseStatement
Jon Story

6
Основна проблема чистоти (я думаю) полягає у тому, щоб уникнути написання кругових залежностей у ваших одиничних тестах. Якщо або тести парсера, або тести парсера використовують розширювач, і цей тест еспандера покладається на те, що аналізатор працює, то у вас є важкий ризик управління тим, що все, що ви протестуєте, полягає в тому, що парсер і розширювач є послідовними , тоді як що ви хотіли зробити тест на те, що розширювач насправді робить те, що належить . Але поки немає залежності від іншого способу, використання аналізатора в цьому модульному тесті насправді не відрізняється від використання стандартної бібліотеки в одиничному тесті.
Стів Джессоп

@SteveJessop Добрий момент. Важливо використовувати незалежні компоненти.
Джонатан Юніс

3
Щось я робив у випадках, коли сам аналізатор є дорогою операцією (наприклад, зчитування даних з файлів Excel через com interop) - це написання методів генерації тестів, які запускають парсер і вихідний код до консолі, відтворюють структуру даних повернення парсера . Потім я копіюю вихід з генератора на більш звичайні одиничні тести. Це дозволяє зменшити перехресну залежність, оскільки аналізатор повинен працювати правильно лише тоді, коли тести створювалися не кожен раз, коли вони виконуються. (Не витрачати кілька секунд / тест на створення / знищення процесів Excel був приємним бонусом.)
Dan Neely

+1 для підходу @ DanNeely Ми використовуємо щось подібне для зберігання декількох серіалізованих версій нашої моделі даних як тестових даних, так що ми можемо бути впевнені, що новий код може ще працювати зі старими даними.
Кріс Хейс

6

Звичайно, це нормально!

Вам завжди потрібен функціональний / інтеграційний тест, який здійснює повний шлях коду. І повний шлях коду в цьому випадку означає, включаючи оцінку згенерованого коду. Це ви перевіряєте, що для розбору створюється x = 2 + 3 * aкод, який, якщо запустити з, a = 5буде встановлено xна, 17а якщо запустити з a = -2буде встановлено xна -4.

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


4

Тестові одиниці дозволяють зафіксувати конкретні предмети, які ламаються та де в коді вони зламані. Тож вони гарні для дуже дрібнозернистих тестувань. Хороші одиничні тести допоможуть скоротити час налагодження.

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

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


0

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

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