Как будут обнаружены ошибки при создании макетов на динамическом языке?

10

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

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

Обновление с тривиальным образцом (на некоторых выдуманных языках) ...

Версия 1:

Calc = {
    doMultiply(x, y) {return x * y}
}
//.... more code ....

// On some faraway remote code on a different file
Rect = {
    computeArea(l, w) {return Calc.doMultipy(x*y)}
}

// test for Rect
testComputeArea() { 
    Calc = new Mock()
    Calc.expect(doMultiply, 2, 30) // where 2 is the arity
    assertEqual(30, computeArea)
}

Теперь о версии 2:

// I change the return types. I also update the tests for Calc
Calc = {
    doMultiply(x, y) {return {result: (x * y), success:true}}
}

... Rect затем выдаст исключение во время выполнения, но тест все равно будет успешным.

jvliwanag
источник
1
То, что до сих пор кажется, что ответы пропущены, так это то, что вопрос не в тестах, связанных с измененными class X, а в том, от class Yкаких тестов зависит Xи, таким образом, их тестируют по контракту, отличному от того, с которым он работает в производстве.
Барт ван Инген Шенау
Я только что задал этот вопрос самому себе в отношении внедрения зависимостей. См. Причина 1. Зависимый класс может быть изменен во время выполнения (подумайте о тестировании) . У нас обоих одинаковое мышление, но нам не хватает хороших объяснений.
Скотт Коутс
Я перечитываю ваш вопрос, но меня немного смущает интерпретация. Можете ли вы привести пример?
Скотт Коутс

Ответы:

2

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

Для меня проблема заключается в использовании других методов рефакторинга, чем в статически типизированном языке. В статическом языке вы частично заменяете типы возвращаемых данных, чтобы вы могли «опираться на компилятор», чтобы найти места, которые могут сломаться. Это безопасно и, вероятно, безопаснее, чем модифицировать возвращаемый тип на месте.

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

tallseth
источник
2

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

Что я имею в виду под этим? Ну, ваши тесты описывают контракты между частями вашего кода. Если в контракте указано «возвращаемое значение должно быть итеративным», то изменение возвращаемого значения, скажем, массива в список, на самом деле не является изменением контракта и, следовательно, не обязательно приведет к провалу теста.

Для того , чтобы не пропустить утверждения, вы можете использовать такие инструменты, как анализ покрытия коды (он не скажет вам , какие части вашего кода тестирует, но это будет вам сказать , что детали определенно не тестировались), цикломатическая сложность и сложность NPath (которые дают вам нижнюю границу на ряд утверждений , необходимых для достижения полного C1 и C2 покрытия кода) и мутации тестировщиков (которые INJECT мутации в вашем коде , такие как поворот trueк false, отрицательные числа в положительную, объекты в и nullт.д. , и проверить , что делает тесты провальными).

Йорг Миттаг
источник
+1 Что-то не так с тестами или код не изменился. Мне потребовалось некоторое время, чтобы привыкнуть после многих лет C ++ / Java. Контракт между частями в динамических языках кода должен заключаться не в том, ЧТО возвращается, а в том, что содержит эта возвращаемая вещь и что она может сделать.
MrFox
@suslik: На самом деле речь идет не о статических и динамических языках, а о абстракции данных с использованием абстрактных типов данных и объектно-ориентированной абстракции данных. Способность одного объекта моделировать другой объект, пока он ведет себя неразличимо (даже если они совершенно разных типов и экземпляров совершенно разных классов), является фундаментальной для всей идеи ОО. Или другими словами: если два объекта ведут себя одинаково, они относятся к одному и тому же типу независимо от того, что говорят их классы. Иными словами, классы не являются типами. К сожалению, Java и C # ошибаются.
Йорг Миттаг,
Предполагая, что проверяемая единица покрыта на 100%, и нет пропущенного утверждения: как насчет более тонкого изменения, такого как «должен вернуть ноль для foo» в «должен вернуть пустую строку для foo». Если кто-то меняет этот юнит и его тест, некоторые потребляющие юниты могут сломаться, и из-за имитации это прозрачно. (И нет: во время написания или использования смоделированного модуля, согласно «контракту», необязательно обрабатывать пустые строки как возвращаемое значение, поскольку предполагается, что они возвращают непустые строки или только ноль;)). (Только правильное интеграционное тестирование обоих модулей во взаимодействии может это уловить.)
try-catch-finally