TDD, как обрабатывать изменения в макете объекта - PullRequest
15 голосов
/ 06 марта 2012

При написании юнит-тестов, для каждого объекта, с которым взаимодействует юнит, я предпринимаю следующие шаги (украденные из моего понимания Интеграционных тестов Дж. Брейнса ):

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

Мой вопрос возникает, когда я решаю провести рефакторинг объекта, чьи ответы высмеиваются на шаге 2. Если я изменю способ, которым объект отвечает на вызов, ни один из тестов, которые другиеобъекты для этого вызова потерпят неудачу, потому что все они были смоделированы, чтобы соответствовать старому стилю.Как вы держите в курсе издевательства над объектами, над которыми они издеваются?Есть ли лучшая практика для этого?Или я совершенно неправильно понял и все делаю неправильно?

Ответы [ 4 ]

3 голосов
/ 06 марта 2012

Я делаю это так.

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

Теперь я создаюветка управления версиями, потому что это будет какое-то время беспорядочноодним.Я получаю их все мимоходом.Я могу сделать это, не касаясь какой-либо производственной реализации foo().

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

На этом этапе все тесты совместной работы теперь предполагают новые ответы от foo(), а тесты контракта / тесты реализации теперь ожидают новых ответов отfoo(), так что все должно работать. (ТМ)

Теперь объедините свою ветвь и налейте себе немного вина.

2 голосов
/ 06 марта 2012

Исправлено: это компромисс.Простота тестирования за счет изоляции объекта от его среды и уверенности в том, что все это работает, когда все части объединяются.

  1. Стремитесь к стабильным ролям: мыслите с точки зрения ориентированных на клиента ролей (а не группы)методов).Я обнаружил, что роли (написанные с точки зрения потребностей клиента / клиента сначала / снаружи) менее изменчивы.Проверьте, является ли роль утечкой абстракции, предающей детали реализации.Также обратите внимание на роли, которые являются магнитами изменений (и придумали план смягчения последствий).
  2. Если у вас есть для внесения изменений, посмотрите, можете ли вы 'опираться на компилятор«.Такие вещи, как изменение сигнатуры метода, будут хорошо помечены компилятором.Так что используйте его.
  3. Если компилятор не может помочь вам определить изменения, будьте более усердны, чем обычно , чтобы увидеть, не пропустили ли вы место (использование клиента).
  4. Наконец, вы вернетесь к приемочным тестам для выявления таких проблем - убедитесь, что Объект A и сотрудники B, C, D используют одни и те же предположения (протокол).Если что-то удастся избежать вашего драгнета, велика вероятность, что хотя бы один тест обнаружит его.
0 голосов
/ 12 декабря 2017

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

Я слышал, ты спрашиваешь?

Тогда каково ваше предложение?

Мое предложение:

  1. Вы должны написать макеты.

  2. Вы должны только написать макеты для программных компонентов, которые вы поддерживает .

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

  4. Вы не должны издеваться над чужим компонентом .

  5. Когда вы пишете модульный тест для вашего компонента, вы должны написать отдельный модульный тест для макета этого компонента. Давайте назовем это MockSynchTest.

  6. В MockSynchTest вы должны сравнить каждое поведение макета с реальным компонентом.

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

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

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

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

Как-к-записи-хорошие тесты # Dont-макет типа вы-Dont собственного

0 голосов
/ 06 марта 2012

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

Ответ на этот вопрос заключается в том, чтобы провести частичные интеграционные тесты, в которых реальные сервисы имеют глубину 1 уровень, но помимо этого имитируют.Например:

var sut = new SubjectUnderTest(new Service1(Mock.Of<Service1A>(), ...), ...);

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

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

// discriminated union
type ResponseType
| Success
| Fail of string   // takes an argument of type string

// a function
let saveObject x =
    if x = "" then
        Fail "argument was empty"
    else
        // do something
        Success

let result = saveObject arg 

// handle response types
match result with
| Success -> printf "success"
| Fail msg -> printf "Failure: %s" msg

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

Эта концепция может иметь большое значение для обработки эволюции программы.,C #, Java, Ruby и другие языки используют исключения для сообщения об ошибках.Но эти условия отказа часто вовсе не являются «исключительными» обстоятельствами, что в итоге приводит к ситуации, с которой вы сталкиваетесь.

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

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