Контракты кода.NET 4.0 - Как они повлияют на модульное тестирование?

Например, эта статья представляет их.

В чем выгода?

Статический анализ кажется классным, но в то же время он не позволяет передавать нулевое значение в качестве параметра в модульном тесте. (если вы следовали примеру в статье, которая есть)

В то время как на тему модульного тестирования - учитывая, как обстоят дела сейчас, наверняка, нет смысла для контрактов кода, если вы уже практикуете автоматизированное тестирование?

Обновить

Поиграв с Code Contracts, я немного разочарован. Например, на основе кода в принятом ответе:

public double CalculateTotal(Order order)
{
    Contract.Requires(order != null);
    Contract.Ensures(Contract.Result<double>() >= 0);
    return 2.0;
}

Для модульного тестирования вам все еще нужно написать тесты, чтобы гарантировать, что нулевое значение не может быть передано, а результат больше или равен нулю, если контракты являются бизнес-логикой. Другими словами, если бы я должен был удалить первый контракт, никакие тесты не прервались бы, если у меня специально не было теста для этой функции. Однако это основано на том, что не используется статический анализ, встроенный в лучшие (окончательные и т.д.) версии Visual Studio.

По сути, все они сводятся к альтернативному способу написания традиционных операторов if. Мой опыт использования TDD с Code Contracts показывает, почему и как я это сделал.

4 ответа

Решение

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


public class Order
{
    public IEnumerable Items { get; }
}

public class OrderCalculator
{
    public double CalculateTotal(Order order)
    {
        Contract.Requires(order != null);
        Contract.Ensures(Contract.Result<double>() >= 0);

        return 2.0;
    }
}

Ясно, что код удовлетворяет условиям контракта, но вам все равно потребуется модульное тестирование, чтобы проверить, что он действительно ведет себя так, как вы ожидаете.

В чем выгода?

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

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

Меньше работы, безупречная гарантия правильности. Что не нравится?

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

Например, если я объявляю целочисленный параметр (используя нотацию контракта) в диапазоне от 1 до 10, и у меня есть локальный массив в моей функции, объявленный такого же размера, который индексируется параметром, компилятор может сказать что нет возможности ошибки индексации, тем самым создавая лучший код.

Вы можете указать, что значение NULL является действительным в контракте.

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

Ну, это не помешает юнит-тестированию в целом. Но, как я увидел, вы упомянули кое-что о TDD.

Если я подумаю об этом с этой точки зрения, я думаю, что это может / может изменить процедуру от стандартной

  • создать метод (просто подпись)
  • создать модульный тест -> внедрить тест
  • запустить тест: пусть он провалится
  • реализовать метод, взломать его до конца, чтобы заставить его работать
  • запустить тест: увидеть его пройти
  • рефакторинг вашего (возможно, грязного) тела метода
  • (повторите тест, чтобы убедиться, что вы ничего не сломали)

Это будет действительно сложная полнофункциональная процедура юнит-тестирования. В таком контексте, я думаю, вы могли бы вставить кодовые контракты между 1-й и 2-й точкой, как

  • создать метод (просто подпись)
  • вставить код контракты для входных параметров методов
  • создать модульный тест -> внедрить тест
  • ...

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

редактировать

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

Другие вопросы по тегам