Как я препятствую тому, чтобы мои модульные тесты требовали знания о внутренностях реализации при использовании фиктивных объектов?

Я нахожусь все еще на этапах изучения относительно поблочного тестирования и в особенности относительно насмешки (я использую PascalMock и платформы DUnit). Одна вещь, которую я теперь споткнулся, состояла в том, что я не мог найти путь вокруг деталей реализации жесткого кодирования протестированного класса/интерфейса в мой модульный тест и который просто чувствует себя неправильным...

Например: Я хочу протестировать класс, который реализует очень простой интерфейс для чтения и записи параметров настройки приложения (в основном пар имя/значение). Интерфейс, который представлен потребителю, является абсолютно агностическим туда, где и как значения на самом деле хранятся (например, реестр, INI-файл, XML, база данных, и т.д.). Естественно, слой доступа реализован все же другим классом, который введен в протестированный класс на конструкции. Я создал фиктивный объект для этого слоя доступа, и я теперь могу полностью протестировать интерфейсный класс с реализацией, на самом деле не читая или пишущий что-либо в любой registry/INI-file/whatever.

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

8
задан Oliver Giesen 10 August 2010 в 10:47
поделиться

3 ответа

Это то, с чем я должен буду просто жить с этим при использовании макетов или есть лучший способ проектирования зависимости классов, чтобы избежать

Много раз моки (особенно чувствительные фреймворки, такие как JMock) заставляют вас учитывать детали, которые не относятся непосредственно к поведению, которое вы пытаетесь протестировать, и иногда это может быть даже полезно, раскрывая подозрительный код, который делает слишком много и имеет слишком много вызовов/зависимостей.

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

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

Разве смысл написания абстрактного уровня доступа не в том, чтобы избежать этого? В общем, следуя принципу Открыто/Закрыто, интерфейс такого рода не должен меняться и не должен нарушать контракт с классом, который его использует, и, следовательно, он не нарушит и ваши модульные тесты. Теперь, если вы измените порядок вызовов методов, или вам придется делать новые вызовы к абстрактному слою, то да, особенно в некоторых фреймворках, ваши имитационные ожидания будут нарушены. Это просто часть издержек использования макетов, и это вполне приемлемо. Но сам интерфейс, в общем, должен оставаться стабильным.

6
ответ дан 5 December 2019 в 12:06
поделиться

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

Правильно.

изменения в интерфейсе уровня доступа или в том, как тестируемый класс использует этот уровень, я также должен буду изменить модульные тесты

Верно.

даже если интерфейс класса, который я фактически тестирую, не изменился.

"На самом деле тестирую"? Вы имеете в виду открытый интерфейс класса? Это нормально.

То, как "тестируемый" (интерфейсный) класс использует уровень доступа, означает, что вы изменили внутренний интерфейс к уровню доступа. Изменения интерфейса (даже внутреннего) требуют изменения тестов и могут привести к поломке, если вы сделали что-то не так.

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

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

Тесты должны работать только по правильной причине.

7
ответ дан 5 December 2019 в 12:06
поделиться

Чтобы добавить несколько имен к вашему примеру,

  • RegistryBasedDictionary реализует словарь ролей (интерфейса).
  • RegistryBasedDictionary зависит от Role RegistryAccessor, реализованного с помощью RegistryWinAPIWrapper.

В настоящее время вы заинтересованы в тестировании RegistryBasedDictionary. Модульные тесты будут вводить фиктивную зависимость для роли RegistryAccessor и проверять ожидаемое взаимодействие с зависимостями.

  • Уловка, позволяющая избежать ненужного обслуживания тестов, заключается в том, чтобы « точно указать, что должно произойти ... и не более того. » (Из книги GOOS (необходимо прочитать для имитации модифицированный TDD), поэтому, если порядок вызовов методов зависимостей не имеет значения, не указывайте его в тесте. Это дает вам возможность изменять порядок вызовов в реализации.)
  • Разработайте роли таким образом, чтобы они не содержат утечек из фактических реализаций - оставить Роли независимыми от реализации .

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

1
ответ дан 5 December 2019 в 12:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: