Контрактное программирование против модульного теста

13

Я несколько оборонительный программист и большой поклонник контрактов Microsoft.

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

class
{       
    function()
    {   
         checkInvariants();
         assert(/* requirement */);

         try
         {
             /* implementation */
         }
         catch(...)
         {
              assert(/* exceptional ensures */);                  
         }
         finally
         {
              assert(/* ensures */);
              checkInvariants();
         }
    }

    void checkInvariants()
    {
         assert(/* invariant */);
    }
}

Однако эта парадигма (или как бы вы ее ни называли) приводит к большому количеству помех в коде.

Я начал задаваться вопросом, действительно ли это стоит усилий и охватит ли это надлежащий модульный тест?

ronag
источник
6
В то время как модульные тесты позволяют убрать утверждения из кода приложения (и, следовательно, избежать беспорядка), имейте в виду, что они не могут проверить все, что может произойти в реальной производственной системе. Таким образом, контракты IMO-кода имеют некоторые преимущества, особенно для критического кода, где правильность особенно важна.
Джорджио
Таким образом, это в основном время разработки, ремонтопригодность, удобочитаемость и лучшее покрытие кода?
Ронаг
1
В основном я использую утверждение в коде, чтобы проверить правильность параметров (например, при нулевом значении), а в модульном тесте дополнительно проверить эти утверждения.
artjom

Ответы:

14

Я не думаю, что вы должны думать об этом как "против".
Как упомянуто в комментариях @Giorgio, контракты кода должны проверять инварианты (в производственной среде), а модульные тесты должны гарантировать, что ваш код работает должным образом при соблюдении этих условий.

Duros
источник
2
Я думаю, что также важно проверить, работает ли код (например, он генерирует исключение), когда условия не выполняются.
svick
6

Контракты помогают вам, по крайней мере, с одной вещью, которую не дают юнит-тесты. Когда вы разрабатываете публичный API, вы не можете тестировать модули, как люди используют ваш код. Однако вы все еще можете определить контракты для ваших методов.

Лично я бы так строго относился к контрактам только при работе с публичным API модуля. Во многих других случаях это, вероятно, не стоит усилий (и вместо этого вы можете использовать модульные тесты), но это только мое мнение.

Это не значит, что я советую не думать о контрактах в этих случаях. Я всегда думаю о них. Я просто не думаю, что необходимо всегда явно их кодировать.

Хонза Брабек
источник
1

Как уже упоминалось, контракты и модульные тесты имеют различное назначение.

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

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

Утверждения хороши, они делают код устойчивым. Однако, если вы беспокоитесь о том, что он добавляет много кода, вы также можете добавить условные точки останова в некоторых местах во время отладки и уменьшить количество утверждений.

Саджад Деяргароо
источник
0

Все, что у вас есть в вызовах checkVariants (), может быть сделано из тестирования, сколько усилий на самом деле может быть, зависит от многих вещей (внешних зависимостей, уровней связи и т. Д.), Но это очистит код с одной точки зрения. Насколько поддается проверке основанная на утверждениях кодовая база без какого-либо рефакторинга, я не уверен.

Я согласен с @duros, они не должны рассматриваться как исключительные или конкурирующие подходы. На самом деле в среде TDD можно даже утверждать, что утверждения «требования» должны будут иметь тесты :).

Утверждения, однако, НЕ делают код более надежным, если вы на самом деле не делаете что-то для исправления неудачной проверки, они просто останавливают повреждение данных или подобное, обычно прерывая обработку при первых признаках проблемы.

Тестируемое / хорошо протестированное решение обычно уже задумывалось и / или обнаруживало многие из источников / причин неправильных входных и выходных данных при разработке взаимодействующих компонентов и уже рассматривало их ближе к источнику проблемы.

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

Кроме того, мне любопытно, на каких языках вы используете, у которых нет какой-либо xUnit или другой библиотеки тестирования, которую кто-то разработал, я думал, что в наши дни есть что-то для всего этого?

Крис Ли
источник
0

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

Это не всегда легко или возможно, но определенно стоит задать себе вопрос: «Можно ли сделать этот код более надежным?»

Андерс Хейлсберг, создатель C #, сказал, что одной из самых больших ошибок в C # не было включение необнуляемых ссылочных типов. Это одна из главных причин, по которой существует столь необходимый беспорядок в защитном коде.

Тем не менее, рефакторинг для получения только необходимого и достаточного количества защитного кода делает, по моему опыту, более пригодным для использования и обслуживаемым кодом.

Согласитесь с @duros в остальном.

Мир джеймса
источник
0

Сделайте оба, но сделайте несколько статических вспомогательных методов, чтобы уточнить ваши намерения. Это то, что Google сделал для Java, посмотрите code.google.com/p/guava-libraries/wiki/PreconditionsExplained

Александр Торстлинг
источник