Реорганизация метода, имеющего зависимости внутри одного и того же объекта, в нечто более тестируемое (PHP) - PullRequest
1 голос
/ 08 апреля 2009

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


class MyClass
{
    public function myMethod()
    {
        $var1 = $this->otherMethod1();
        $var2 = $this->otherMethod2();
        $var3 = $this->otherMethod3();
        $otherObject = new OtherClass();
        $var4 = $otherObject->someMethod();

        # some processing goes on with these 4 variables
        # then the method returns something else

        return $var5;
    }
}

Я новичок во всей игре TDD, но кое-что из того, что я думаю Я понял, что ключевыми предпосылками для более тестируемого кода являются композиция, слабая связь, с некоторой стратегией для внедрения зависимостей / инверсии Контроль.

Как мне провести рефакторинг метода во что-то более тестируемое в этой конкретной ситуации?

Передавать ли ссылку на объект $this на метод в качестве параметра, чтобы я мог легко смоделировать / заглушить совместные методы? Это рекомендуется или идет за борт?


class MyClass
{
    public function myMethod($self, $other)
    {
        # $self == $this
        $var1 = $self->otherMethod1();
        $var2 = $self->otherMethod2();
        $var3 = $self->otherMethod3();
        $var4 = $other->someMethod();

        # ...

        return $var5;
    }
}

Кроме того, для меня очевидно, что зависимости являются довольно большой проблемой в TDD, поскольку нужно подумать о том, как добавить заглушку / макет в указанный метод для тестов. Большинство TDD используют DI / IoC в качестве основной стратегии для публичных зависимостей? в какой момент это становится преувеличенным? Вы можете дать несколько советов, чтобы сделать это эффективно?

Ответы [ 5 ]

1 голос
/ 08 апреля 2009

Похоже, сама идея этого класса не совсем верна. В TDD вы тестируете классы, но не методы. Если у метода есть своя ответственность и он предоставляет свою собственную (отдельную тестируемую) функциональность, его следует перенести в отдельный класс. В противном случае это просто нарушает всю инкапсуляцию ООП. В частности, это нарушает принцип единой ответственности.

В вашем случае я бы извлек тестируемый метод в другой класс и вставил $var1, $var2, $var3 и $other в качестве зависимостей. $other должен быть смоделирован, также как и любой объект, от которого проверяется класс.

class TestMyClass extends MyTestFrameworkUnitTestBase{
     function testMyClass()
     {
          $myClass = new MyClass();
          $myClass->setVar1('asdf');
          $myClass->setVar2(23);
          $myClass->setVar3(78);
          $otherMock = getMockForClassOther();
          $myClass->setOther($otherMock);
          $this->assertEquals('result', $myClass->myMethod());
     }
}

Основное правило, которое я использую: если я хочу что-то протестировать, я должен сделать это классом. Это не всегда верно в PHP, хотя. Но это работает в PHP в 90% случаев. (Исходя из моего опыта)

1 голос
/ 08 апреля 2009

Это хорошие вопросы ... позвольте мне сначала сказать, что я на самом деле совсем не знаю JS, но я являюсь юнит-тестером и разбираюсь с этими проблемами. Сначала я хочу указать, что JsUnit существует, если вы его не используете.

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

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

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

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

0 голосов
/ 05 августа 2009

Возможно, это скорее нарушение принципа единой ответственности, что приводит к проблемам TDD.

Это ХОРОШО, это означает, что TDD раскрывает недостатки дизайна. Или так история идет.

Если эти методы не являются общедоступными, и вы просто разбиваете код на более усваиваемые куски, честно, мне было бы все равно.

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

0 голосов
/ 08 апреля 2009

Есть несколько вещей, которые вы можете сделать:

Лучшее, что можно сделать - это макет, вот одна из таких библиотек: http://code.google.com/p/php-mock-function

Это должно позволить вам макетировать только те функции, которые вы хотите.

Если это не сработает, следующая лучшая вещь - предоставить реализацию method2 как метод объекта в классе MyClass. Я считаю это одним из самых простых методов, если вы не можете смоделировать методы напрямую:

class MyClass {
  function __construct($method2Impl) {
    $this->method2Impl = $method2Impl;
  }
  function method2() {
    return $this->method2Imple->call();
  }
}

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

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

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

0 голосов
/ 08 апреля 2009

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

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