Можно ли вводить методы, которые используются только во время модульных тестов?

12

Недавно я был TDDing заводским методом. Метод заключался в создании либо простого объекта, либо объекта, завернутого в декоратор. Декорированный объект может быть одного из нескольких типов, расширяющих StrategyClass.

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

Я пишу код на PHP, чтобы использовать ext/Reflectionкласс обернутых объектов, но мне показалось, что он слишком усложняет вещи и несколько напоминает правила TDD.

Вместо этого я решил представить, getClassName()что при вызове из StrategyClass будет возвращаться имя класса объекта. Однако при вызове из декоратора он возвращает значение, возвращаемое тем же методом в декорированном объекте.

Некоторый код, чтобы сделать его более понятным:

interface StrategyInterface {
  public function getClassName();
}

abstract class StrategyClass implements StrategyInterface {
  public function getClassName() {
    return \get_class($this);
  }
}

abstract class StrategyDecorator implements StrategyInterface {

  private $decorated;

  public function __construct(StrategyClass $decorated) {
    $this->decorated = $decorated;
  }

  public function getClassName() {
    return $this->decorated->getClassName();
  }

}

И тест PHPUnit

 /**
   * @dataProvider providerForTestGetStrategy
   * @param array $arguments
   * @param string $expected
   */
  public function testGetStrategy($arguments, $expected) {

    $this->assertEquals(
      __NAMESPACE__.'\\'.$expected,  
      $this->object->getStrategy($arguments)->getClassName()
    )
  }

//below there's another test to check if proper decorator is being used

Моя точка зрения здесь: нормально ли вводить такие методы, которые не имеют другого применения, кроме как облегчить юнит-тесты? Почему-то мне это не кажется правильным.

MCHL
источник
Возможно, этот Вопрос поможет вам лучше разобраться в вашем вопросе, programmers.stackexchange.com/questions/231237/… Я считаю, что это зависит от использования и того, будут ли методы очень полезны для модульного тестирования и отладки любого приложения, которое вы разрабатываете. .
Климент Mark-Aaba

Ответы:

13

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

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

Джереми
источник
+1 ОП описывает юнит-тестирование «чистой коробки», а не тестирование функции TDD
Стивен А. Лоу,
1
Я вижу смысл этого, хотя я немного неохотно добавляю тестирование алгоритмов StrategyClass, когда я хочу проверить, выполняет ли фабричный метод свою работу. Такого рода разрывы изоляции ИМХО. Еще одна причина, по которой я хочу избежать, заключается в том, что эти конкретные классы работают с базой данных, поэтому их тестирование требует дополнительной насмешки.
Mchl
С другой стороны, и в свете этого вопроса: programmers.stackexchange.com/questions/86656/… когда мы отличаем «тесты TDD» от «модульных тестов», это становится совершенно нормально (хотя все еще работает с базой данных: P)
Mchl
Если вы добавите методы, они станут частью договора с вашими пользователями. В итоге вы получите кодировщиков, которые вызывают ваши функции только для тестирования и переходят на основе результатов. Вообще, я предпочитаю выставлять как можно меньше учеников.
BillThor
5

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

Мой предпочтительный способ справиться с этим (и я должен извиниться за то, что не могу показать, как это сделать в PHP, поскольку я в основном пишу на языках стиля C), состоит в том, чтобы предоставлять «тестовые» функции таким образом, чтобы они не подвергается воздействию внешнего мира самим объектом, но может быть доступно производным объектам. Для целей тестирования я бы затем получил класс, который обрабатывал бы взаимодействие с объектом, который я на самом деле хочу протестировать, и чтобы модульный тест использовал этот конкретный объект. Пример C ++ будет выглядеть примерно так:

Тип производства класс:

class ProdObj
{
  ...
  protected:
     virtual bool checkCertainState();
};

Тест помощника класса:

class TestHelperProdObj : public ProdObj
{
  ...
  public:
     virtual bool checkCertainState() { return ProdObj::checkCertainState(); }
};

Таким образом, вы, по крайней мере, находитесь в положении, когда вам не нужно показывать функции типа «тест» в вашем главном объекте.

Тимо Гюш
источник
Интересный подход. Мне нужно посмотреть, как я могу это адаптировать
Mchl
3

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

Тестирование важно, если нужно, просто добавьте что-нибудь для него.

Но попробуйте некоторые из альтернатив. Ваш вариант на основе отражения не так уж и плох. Вы можете иметь защищенный виртуальный метод доступа к тому, что вам нужно, и создать производный класс для тестирования и утверждения. Возможно, вы можете разделить свой класс и напрямую протестировать получившийся листовой класс. Скрытие метода теста с переменной компилятора в вашем исходном коде тоже вариант (я почти не знаю PHP, не уверен, возможно ли это в PHP).

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

Joppe
источник
1

Я абсолютный новичок TDD, но, похоже, это зависит от добавляемого метода. Исходя из того, что я понимаю в TDD, предполагается, что ваши тесты в какой-то степени "стимулируют" создание вашего API.

Когда все в порядке:

  • Пока метод не нарушает инкапсуляцию и служит цели, которая соответствует ответственности объекта.

Когда все не в порядке:

  • Если метод кажется чем-то, что никогда не будет полезным или не имеет смысла по отношению к другим интерфейсным методам, он может быть непонятным или запутанным. В этот момент это испортило бы мое понимание этого объекта.
  • Пример Джереми: «... когда вам нужно знать, насколько эффективна какая-то подпрограмма, например, использует ли ваш Hashmap равномерно распределенный диапазон значений хеша - ничто во внешнем интерфейсе не сможет вам этого сказать».
щенок
источник