Класс тестирования с частной / защищенной константой - PullRequest
0 голосов
/ 31 октября 2018

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

class FooBar
{
    const RANDOM = 18;
}
....
// Somewhere in test...
$this->assertEquals(FooBar::RANDOM, $mock->doSomething());

Теперь, начиная с PHP 7.1, можно определять константы класса с модификатором видимости, это можно изменить на:

private const RANDOM = 18;

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

Так что теперь у нас есть два варианта:

  1. Объявите константу как общедоступную.
  2. Использовать отражение в тесте. Значение теста становится:

$this->assertEquals( (new ReflectionClass(FooBar::class))->getConstant('RANDOM'), $mock->doSomething() );

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

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

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

Ответы [ 2 ]

0 голосов
/ 08 июня 2019

Использование константы в тесте на самом деле плохая практика ИМХО.

Вы должны проверить буквальное значение константы. ($this->assertSame(18, $mock->doSomething())

Почему?

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

Теперь представьте, что младший разработчик, который не знаком с базой кода, должен изменить одно из мест, где используется константа, и изменить его с 18 на 16. Он пойдет и изменит значение константы с 18 на 16 и сделайте грубую проверку того, где используется константа (не обращая внимания на ваш метод doSomething ()). Теперь, в вашем методе вам нужно, чтобы случайным было 18, а не 16! Но если бы вы использовали константу, он никогда бы не узнал, потому что, когда он изменил ее с 18 на 16, утверждение также изменится с 18 на 16. И тест пройдёт.

Правило для меня:

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

0 голосов
/ 28 февраля 2019

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

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

Итак, что, если вышеупомянутое не может быть сделано разумным способом? Я не знаю, что предлагает вам PHP, но на других языках у вас есть больше возможностей, чем просто публичный и приватный: в C ++ у вас есть концепция друзей, которую можно использовать, чтобы сделать тестовый класс другом класса, подлежащего тестированию. У классов друзей есть доступ ко всем частным деталям.

Если это не вариант в PHP, вы могли бы на самом деле увеличить видимость (используя public, но, возможно, есть также защищенный или локальный пакет?). Иногда это хороший вариант, и здесь вы можете провести логическое различие между «публично видимым» и «предназначенным для публичного использования». Чтобы сделать это более очевидным (и заставить пользователей по-настоящему воздерживаться от использования таких интерфейсов), вы можете назвать их безобразно. Для вашей константы вы можете объявить метод получения for_testing_only__get_constant или что-то еще хуже.

Многим людям, похоже, трудно изменить видимость и провести различие между «общедоступными» и «предназначенными для публичного использования». Возможно, потому что это требует большей дисциплины во время разработки и управленческой основы во времена сжатых сроков поставки или по любой другой причине. Тем не менее, это необходимая концепция во многих языках (более старых, таких как C, но Python использует ее намеренно). И, строго говоря, при самоанализе все равно нет настоящей конфиденциальности.

Тем не менее, возможность сделать что-то явно «публично видимым», но не «предназначенным для публичного использования» путем присвоения имен, является более чистым подходом IMO, чем просто самоанализ: он позволяет вводить и сообщать дополнительные уровни конфиденциальности, например » действительно личное, и я имею в виду это "," личное для всех, кроме тестирования "," публичное ". Если вы просто используете вместо этого самоанализ, это различие не проводится. (Тем не менее, вы, конечно, можете переименовать приватную константу в constant__access_allowed_for_testing - но это влияет на читаемость также везде в классе, где используется константа.)

TL; DR: попробуйте использовать открытый интерфейс и помните, что это означает тестирование деталей реализации через открытый интерфейс. Если использование общедоступного API невозможно сделать разумно, сделайте вещи «общедоступными, но не предназначенными для публичного использования» и сообщите об этом путем присвоения имен. Не подходи к самоанализу.

...