Можно ли отказаться от избыточных тестов в методологии TDD?

У меня есть две функции, foo и bar.

function foo() {
    if (arguments.length !== 1) {
        throw new TypeError('only one argument expected');
    }

    do work;
}

function bar() {
    if (arguments.length !== 3) {
        throw new TypeError('exactly three arguments expected');
    }

    do work;
}

Эти функции были созданы в соответствии с TDD — например. Сначала были написаны тесты, затем реализованы функции, обеспечивающие их прохождение. Список тестов:

  1. foo() определено
  2. foo() делает свою работу правильно
  3. foo() выдает TypeError при неправильном количестве аргументов
  4. bar() определено
  5. bar() делает свою работу правильно
  6. bar() выдает TypeError при неправильном количестве аргументов

Как видите, обе эти функции имеют дублирующий функционал — они проверяют правильность аргументов каждая сама по себе. Поскольку эта проверка будет очень распространена среди моих функций и может стать более сложной (проверка количества аргументов в каком-то диапазоне, проверка типов аргументов и т. д.), я решил переместить ее в отдельную функцию-декоратор check.

Итак, я написал тесты для check и реализовал саму check.

Теперь шаг рефакторинга — я воспользовался преимуществом check, поэтому теперь функции выглядят следующим образом:

foo = check(1, function() {
    do work;
});

bar = check(3, function() {
    do work;
});

Что мне теперь делать, чтобы тесты, проверяющие foo() и bar(), принимали правильное количество аргументов? Могу ли я бросить их?

Когда я реализую функцию buzz(), которая немедленно использует check() (поскольку теперь она используется как способ определения функций), нужно ли мне писать тест, чтобы она принимала правильное количество аргументов? Я сомневаюсь, потому что этот тест всегда будет зеленым, так как вся работа выполняется в декораторе, так что есть ли необходимость в тесте?


person toriningen    schedule 01.02.2014    source источник


Ответы (4)


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

  1. Если вы намеренно измените функцию check таким образом, чтобы нарушить ваши тесты, вам придется изменить 2 теста вместо одного.
  2. Если вы непреднамеренно сломаете check, вы получите две ошибки: одну, относящуюся к foo, и одну, относящуюся к bar, и не может быть очевидным, что нарушена функция check.

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

Таким образом, вы можете в будущем изменить foo или bar, чтобы использовать, например, функцию check2 без тестов проверки функции проверки.

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

person Andrea Parodi    schedule 01.02.2014
comment
Я уже использую «фальшивую» локальную функцию для проверки области действия, чтобы проверить, правильно ли check передает аргументы внутри обернутой функции и возвращает результат функции — например. Я не использую никаких реальных функций для проверки check достоверности. Это ты предложил? - person toriningen; 01.02.2014
comment
да. Если у вас уже есть тесты для функции check, удалите из набора тесты 3 и 6, они лишние. Если вы стремитесь к совершенству, я бы написал мокап для функции check, сделал бы его инжектируемым и просто проверил бы, что вызывается внутри foo и bar. - person Andrea Parodi; 01.02.2014
comment
Но, как указал @carousel, не пытайтесь тестировать все, иначе вы тратите слишком много времени на тестирование простых вещей, которые никогда не сломаются: я предлагаю вам просто удалить тесты и дать как должное, что foo и bar вызывают функцию check - person Andrea Parodi; 01.02.2014

Классические концепции TDD нуждаются в некоторых дополнительных модификациях, чтобы применить их к динамическому языку, такому как Javascript (из-за отсутствия классов, специфического использования замыканий...). Таким образом, у нас есть такие библиотеки тестирования, как Jasminejs. Я не могу вам явно сказать, что делать с вашими функциями, но я знаю, что не нужно все тестировать. Некоторые вещи действительно настолько очевидны, что их просто нужно проверить на вменяемость. Эмпирическое правило заключается в том, чтобы тестировать то, что может сломаться. Например, проверки, доступ к базе данных, пользовательский ввод...

person Miroslav Trninic    schedule 01.02.2014
comment
Поскольку я новичок в TDD — это, по сути, мой первый проект, который я хочу сделать исключительно с максимально строгим TDD — я сомневаюсь в нарушении подхода «ни одна строка кода не должна быть написана без проверки». Например, если я тестирую только то, что можно сломать, чем это отличается от простого модульного тестирования? - person toriningen; 01.02.2014
comment
Внедрение TDD — сложная задача. Это требует времени, работы и терпения. Самое сложное и конечная цель — разработать приложение путем тестирования. Есть мантра TDD (красный, зеленый, рефакторинг), но требуются небольшие шаги. Если вы новичок в TDD, я бы порекомендовал вам ознакомиться с некоторыми предысториями и простыми примерами. Тестирование калькулятора — это хлеб с маслом TDD. Также есть ката TDD. Тестирование — это гиковский навык :) - person Miroslav Trninic; 01.02.2014

Я думаю, вы на правильном пути, TDD делает более очевидным дублирование кода (параметры проверки кодируют его дублирование, которое должно быть удалено с TDD или без него), и вы слушаете свой тест и разделяете ответственность за проверку, теперь у вас есть две обязанности. :

  • проверка параметров, с его собственным набором тестов.
  • Работа функций, у которых есть собственный набор тестов, предполагающих всегда правильные входы.

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

person AlfredoCasado    schedule 02.02.2014

Если есть отдельное заявление, нужен тест. Итак, в вашем случае вам нужно сохранить тесты, которые требуют, чтобы и foo(), и bar() использовали функцию check. Эти тесты могут быть проще, чем исходные тесты, которые вы написали: например, они могут только проверять наличие ошибки, не заглядывая в сообщение об ошибке.

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

person oberlies    schedule 03.01.2018