Чи є кращий спосіб написання одиничних тестів, ніж серія "AssertEquals"?


12

Ось основний приклад того, яким повинен бути мій тест з використанням квіт:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>

<link rel="stylesheet" href="qunit/qunit-1.13.0.css">
<script src = "qunit/qunit-1.13.0.js"></script>
<script src = "../js/fuzzQuery.js"></script>

<script>

test("Fuzz Query Basics", function()
        {
            equal(fuzzQuery("name:(John Smith)"), "name:(John~ Smith~)");
            equal(fuzzQuery("name:Jon~0.1"), "name:Jon~0.1");
            equal(fuzzQuery("Jon"), "Jon~");
            //etc

        }
    );

</script>
</head>
<body>
    <div id="qunit"></div>
</body>
</html>

Тепер я думав, що це трохи повторюється.

Міг би помістити всі входи / виходи в масив і пропустити через нього.

test("Fuzz Query Basics", function()
        {
            var equals = [
                           ["name:(John Smith)", "name:(John~ Smith~)"],
                           ["name:Jon~0.1", "name:Jon~0.1"],
                           ["Jon", "Jon~"]
                           ];

            for (var i = 0; i<equals.length; i++)
                {
                    equal(fuzzQuery(equals[i][0]), equals[i][1]);               
                }

        }
    );

І це чудово працює.

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

Що стосується читабельності, я не думаю, що це не є переконливим, хоча я, мабуть, віддаю перевагу другому.

Абстрагуючи його далі, ви можете помістити випадки введення / виводу в окремий файл CSV, що може полегшити їх зміни.

Питання - що є загальними умовами щодо написання подібних одиничних тестів?

Чи є причина, що ви не повинні вводити їх у масиви?


Чи скаже вам будь-який із них, яке значення не вдалося?
— JeffO

1
@JeffO - Так - З QUnit - Якщо тест не вдасться, результат показує очікуване значення та фактичне значення.
— dwjohnston

Відповіді:


8

Ваші тести на відновлення мають запах: Умовна логіка випробувань .

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

Друга полягає в тому, що вона затьмарює значення тестів. Ми пишемо Методи тестування, оскільки вони ставлять логіку для тестування заданої поведінки в одному місці і дозволяють нам дати їй описову назву (див . Оригінальну статтю BDD Дена Норта для вивчення значення хороших імен для тестів). Коли ваші тести ховаються всередині однієї функції з forциклом, це затьмарює значення коду для читача. Читач не тільки повинен зрозуміти цикл, він також повинен подумки розгадати всі різні типи поведінки, що перевіряються в циклі.

Рішенням, як завжди, є підвищення рівня абстракції. Використовуйте основу тесту , який дає вам параметрірован тести , як xUnit.NET або контексти робити (відмова від відповідальності: я написав контексти). Це дозволяє групувати тести для тріангуляції однакової поведінки разом природним чином, зберігаючи окремі тести на окремі поведінки.


Добре запитання, до речі
— Бенджамін Ходжсон

1
1) Якщо ви рухаєтесь на рівень абстракції, чи не ховаєте ви ті самі деталі, які, за вашими словами, затьмарені циклом for? 2) тут не застосовуються впевнені параметризовані тести. Здається, тут десь є паралелі, але у мене було багато ситуацій, схожих на ОП, де у мене був набір даних 10-20 значень і просто хотілося запустити їх через SUT. Так, кожне значення є різним і потенційно тестує різні майданчики, але, схоже, насправді "винайдіть" імена тестів для кожного окремого значення було б надмірним. Я знайшов оптимальне співвідношення значення / розмір коду, використовуючи подібні ...
— DXM

... петлі. До тих пір, коли тест не вдасться, аргумент друкує саме те, що не вдалося, розробник має достатню кількість відгуків, щоб точно визначити проблему.
— DXM

@DXM 1) тестова рамка забезпечує параметризовану функцію тесту. Ми довіряємо тестовій рамці неявно, тому не пишемо тестів для неї. 2) параметризовані тести саме для цієї мети: ви робите абсолютно однакові кроки кожного разу, але з різними значеннями введення / виводу. Тестова рамка економить вам необхідність писати імена для кожного з них, запускаючи різні входи через один і той же метод тестування.
— Бенджамін Ходжсон

5

Схоже, ви дуже хочете перевірити одиницю даних, керовану даними. Оскільки ви згадали про використання QUnit, я знайшов плагін, який дозволяє параметризовані тести:

https://github.com/AStepaniuk/qunit-parameterize

З тестом, керованим даними, немає нічого ідеологічного, доки сам тестовий код не є умовним. З огляду на ваш тестовий код, здається, він є дуже хорошим кандидатом на тест, керований даними.

Приклад коду для GitHub README:

QUnit
    .cases([
        { a : 2, b : 2, expectedSum : 4 },
        { a : 5, b : 5, expectedSum : 10 },
        { a : 40, b : 2, expectedSum : 42 }
    ])
    .test("Sum test", function(params) {
        var actualSum = sum(params.a, params.b);
        equal(actualSum, params.expectedSum);
    });

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

1
@RobertHarvey - правильно. Існує прийнятий термін для того, що він намагається досягти, і існує плагін для тестової системи, яка використовується для полегшення написання таких тестів. Я подумав, що варто зазначити у відповіді на майбутнє, ось і все.
— Грег Бургхардт

1

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

Це дозволяє мені негайно сказати, які тести / входи не вдається.


0

Мені подобається твій другий підхід, але я додам 2 бали

  • не використовуйте масиви для зберігання перевірених даних, оскільки робота з індексами - це не зовсім чистий спосіб
  • не використовуйте forпетлі

`

[
    {
        process: "name:(John Smith)",
        result: "name:(John~ Smith~)"
    },
    {
        process: "name:Jon~0.1", 
        result: "name:Jon~0.1"
    },
    {
        process: "Jon", 
        result: "Jon~"
    }
]
.forEach(function(data){

    var result = fuzzQuery(data.process);
    equal(result, data.result);
});

Я не впевнений у квітні, але хороший тестовий бігун покаже, який вхідний рядок не вдався, і який очікуваний результат

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