Модульное тестирование аксессоров (геттеров и сеттеров) - PullRequest
27 голосов
/ 14 февраля 2011

Учитывая следующие методы:

public function setFoo($foo) {
    $this->_foo = $foo;
    return $this;
}

public function getFoo() {
    return $this->_foo;
}

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

  • Как бы вы написали модульные тесты для этих методов?
  • Только один метод испытаний?
  • Должен ли я пропустить эти тесты?
  • Как насчет покрытия кода?
  • Как насчет @covers аннотации?
  • Может быть, какой-нибудь универсальный метод тестирования для реализации в абстрактном тестовом примере?

(я использую Netbeans 7)

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

до цитата из комментария блога Себастьяна Бергмана :

(это похоже на тестирование получателейсеттеры - неудачи!).В любом случае, если они потерпят неудачу;разве методы, которые зависят от них, не сработают?

Итак, как насчет покрытия кода?

Ответы [ 3 ]

8 голосов
/ 30 июня 2013

Если вы делаете TDD, вы должны написать тест для геттера и сеттера.тоже.Не пишите ни одной строки кода без теста для него - даже если ваш код очень прост.

Это своего рода религиозная война, чтобы использовать тандем getter и setter для вашего теста или изолировать каждыйдоступ к защищенным членам класса с использованием возможностей вашего модульного теста.Как тестировщик «черного ящика» я предпочитаю привязывать свой код модульного теста к общедоступному API, а не привязывать его к конкретным деталям реализации.Я ожидаю перемен.Я хочу призвать разработчиков провести рефакторинг существующего кода.И внутренние классы не должны влиять на «внешний код» (модульные тесты в этом случае).Я не хочу нарушать юнит-тесты, когда меняются внутренние компоненты, я хочу, чтобы они ломались, когда меняются публичные API или когда меняется поведение.Хорошо, хорошо, в случае неудачного модульного теста не указывайте на единственный источник проблемы.Я должен посмотреть в получателе и установщике, чтобы выяснить, что вызвало проблему.В большинстве случаев ваш метод получения очень прост (менее 5 строк кода: например, возврат и необязательная проверка нуля с исключением).Так что проверка этого сначала не имеет большого значения и не занимает много времени.А проверка правильности пути установщика в большинстве случаев лишь немного сложнее (даже если у вас есть некоторые проверочные проверки).

Попробуйте изолировать ваши тестовые случаи - напишите тест для SUT (Тема подtest), который проверяет свою корректность без использования других методов (кроме моего примера выше).Чем больше вы изолируете тест, тем больше тестов выявляет проблему.

В зависимости от вашей стратегии тестирования вам может потребоваться пройти только по счастливому пути (прагматичный программист).Или печальные пути тоже.Я предпочитаю охватывать все пути исполнения.Когда я думаю, что обнаружил все пути выполнения, я проверяю покрытие кода, чтобы идентифицировать мертвый код (не чтобы определить, есть ли обнаруженные пути выполнения - 100% покрытие кода является ошибочным индикатором).

Это лучший метод для черного ящикатестировщикам использовать phpunit в строгом режиме и использовать @covers для скрытия сопутствующего покрытия.

Когда вы пишете модульный тест, ваш тест для класса A должен выполняться независимо от класса B. Поэтому ваши модульные тесты для класса A не должныметод call / cover класса B.

Если вы хотите идентифицировать устаревшие методы получения / установки и другие «мертвые» методы (которые не используются рабочим кодом), используйте для этого статический анализ кода.Интересующая вас метрика называется «Афферентная связь на уровне метода (MethodCa)».К сожалению, эта метрика (ca) недоступна на уровне метода в PHP Depend (см .: http://pdepend.org/documentation/software-metrics/index.html и http://pdepend.org/documentation/software-metrics/afferent-coupling.html). Если вам действительно это нужно, не стесняйтесь добавлять ее в PHP Depend.Исключить вызовы из того же класса было бы полезно для получения результата без «побочных» вызовов. Если вы идентифицируете «мертвый метод», попытайтесь выяснить, предполагается ли его использовать в ближайшем будущем (аналог другого метода, который имеетаннотацию @depricated) иначе удалите ее. Если она используется только в том же классе, сделайте ее приватной / защищенной. Не применяйте это правило к библиотечному коду.

План B: Если у вас есть приемочные тесты (интеграционный тест, регрессионный тест и т. д.) вы можете запустить этот тест без одновременного запуска модульных тестов и без строгого режима phpunits. Это может привести к очень похожему результату покрытия кода, как если бы вы анализировали свой рабочий код. Но в большинстве случаевслучаи, когда ваши не юнит-тесты не так сильны, как ваш производственный код. Это зависит от вашей дисциплины, если этот план B "равнозначный »производственному коду для получения значимого результата.

Дополнительная информация: - Книга: Прагматичный программист - Книга: Чистый код

6 голосов
/ 14 февраля 2011

Хороший вопрос,

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

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

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

Чтобы ответить на ваш список:

  • Всего один метод испытаний?

Это мой наименее любимый вариант. Все или нет. Тестирование только одного нелегко для других людей понять и выглядит «случайным» или должно быть задокументировано каким-либо образом.

Редактировать после комментария:

Да, для "тривиального" тестирования get / set я бы использовал только один метод для каждого свойства возможно в зависимости от случая, даже только один метод для всего класса (для объектов-значений с множеством методов получения и установки Я не хочу писать / поддерживать много тестов)

  • Как бы вы написали модульные тесты для этих методов?
  • Должен ли я пропустить эти тесты?

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

  • А как насчет покрытия кода?
  • Как насчет аннотации @covers?

С @covers мой дубль всегда "использовать его везде или не использовать его вообще". Смешивание двух «стилей» тестирования лишает меня некоторых преимуществ аннотации и выглядит «незаконченным» для меня.

  • Может быть, какой-нибудь универсальный метод тестирования для реализации в абстрактном тесте?

Для чего-то вроде ценностных объектов, которые могли бы хорошо работать. Он может сломаться (или усложниться), как только вы передадите объекты / массив с хинтингом типов, но я бы предпочел предпочесть его написанию ручных тестов для 500 геттеров и сеттеров.

3 голосов
/ 14 февраля 2011

Это распространенный вопрос, но, как ни странно, не могу найти обман на SO.

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

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

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

[Test]
public void UpdatesTogglePauseTooltipBasedOnState()
{
    Assert.That(_mainViewModel.TogglePauseTooltip, Is.EqualTo(Strings.Main_PauseAllBeacons));

    _mainViewModel.TogglePauseCommand.Execute(null);
    Assert.That(_mainViewModel.TogglePauseTooltip, Is.EqualTo(Strings.Main_ResumeAllBeacons));

    _mainViewModel.TogglePauseCommand.Execute(null);
    Assert.That(_mainViewModel.TogglePauseTooltip, Is.EqualTo(Strings.Main_PauseAllBeacons));
}
...