2016-12-23 8 views
1

У меня был способ, который открыл соединение сокета, использовал, а затем закрыл его. Чтобы сделать его пригодным для тестирования, я переместил дело с подключением к отдельным методам (см. Код ниже).Как подделать ресурс для модульного теста в PHP?

Теперь я хочу написать единичный тест для barIntrefaceMethod() и вам нужно высмеять метод openConnection(). Другими словами, мне нужна подделка resource.

Можно ли/Как «вручную» создать переменную типа resource в PHP (для того, чтобы поддельные ручки, как «opened files, database connections, image canvas areas and the like» и т.д.)?


FooClass

class FooClass 
{ 
    public function barIntrefaceMethod() 
    { 
     $connection = $this->openConnection(); 
     fwrite($connection, 'some data'); 
     $response = ''; 
     while (!feof($connection)) { 
      $response .= fgets($connection, 128); 
     } 
     return $response; 
     $this->closeConnection($connection); 
    } 

    protected function openConnection() 
    { 
     $errno = 0; 
     $errstr = null; 
     $connection = fsockopen($this->host, $this->port, $errno, $errstr); 
     if (!$connection) { 
      // TODO Use a specific exception! 
      throw new Exception('Connection failed!' . ' ' . $errno . ' ' . $errstr); 
     } 
     return $connection; 
    } 

    protected function closeConnection(resource $handle) 
    { 
     return fclose($handle); 
    } 
} 
+0

Я не уверен, что вы идете по этому правильному пути. Если вы правильно поняли, вы действительно хотите протестировать PHP, встроенный в методы (fwrite, feof и т. Д.), Против объекта focked/fake resource, возвращаемого из '$ resource-> openConnection()'. Вместо этого вы должны рассмотреть объект-обертку вокруг этих методов, чтобы вы могли тестировать '$ resource-> write ('something')' вместо 'fwrite ($ resource, 'something')'. –

+1

Спасибо за ваш комментарий! Нет, конечно, я не буду тестировать собственные функции. И, да, я создал методы, которые обертывают связанные параметры соединения, связанные с функциями PHP. Но теперь я хочу высмеять их результат для тестирования 'barIntrefaceMethod()', который вызывает эти функции переноса. Проблема только в том, что я не нашел способа подделать ['resource'] (http://php.net/manual/en/language.types.resource.php). Это расстраивает насмешку над 'openConnection()'. – automatix

+1

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

ответ

2

насмешливый ресурсов требует магии.

Большинство функций ввода/вывода позволяет использовать protocol wrappers. Эта функция используется vfsStream, чтобы высмеять файловую систему. К сожалению, сетевые функции, такие как fsockopen()не поддерживают его.

Но вы можете переопределить функцию. Я не считаю Runkit и APD, вы можете сделать это без PHP-расширений.

Вы создаете функцию с тем же именем и пользовательской реализацией в текущем пространстве имен. Этот метод описан в другом answer.

Также в большинстве случаев Go! AOP позволяет использовать любую функцию PHP в override. Codeception/AspectMock использует эту функцию для functions mocking. Эта функциональность также поддерживает Badoo SoftMocks.

В вашем примере вы можете полностью переопределить fsockopen() любым способом и использовать vfsStream для вызова функций fgets(), fclose() и другие I/O. Если вы не хотите тестировать метод openConnection(), вы можете высмеять его, чтобы настроить vfsStream и вернуть простой файловый ресурс.

3

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

Ниже я переписан, что у вас есть в вашем вопросе, чтобы продемонстрировать, в каком-то упрощенно и непроизводственной псевдокоде:

Ваш реальный класс:

class FooClass 
{ 
    public function barInterfaceMethod() 
    { 
     $resource = $this->getResource(); 
     $resource->open($this->host, $this->port); 
     $resource->write('some data'); 

     $response = ''; 
     while (($line = $resource->getLine()) !== false) { 
      $response .= $line; 
     } 

     $resource->close(); 

     return $response; 
    } 

    // This could be refactored to constructor injection 
    // but for simplicity example we will leave at this 
    public function getResource() 
    { 
     return new Resource; 
    } 
} 

Ваш тест этого класса:

class FooClassTest 
{ 
    public function testBarInterfaceMethod() 
    { 
     $resourceMock = $this->createMock(Resource::class); 
     $resourceMock 
      ->method('getLine') 
      ->will($this->onConsecutiveCalls('some data', false)); // break the loop 

     // Refactoring getResource to constructor injection 
     // would also get rid of the need to use a mock for FooClass here. 
     // We could then do: $fooClass = new FooClass($resourceMock); 
     $fooClass = $this->createMock(FooClass::class); 
     $fooClass 
      ->expects($this->any()) 
      ->method('getResource'); 
      ->willReturn($resourceMock); 

     $this->assertNotEmpty($fooClass->barInterfaceMethod()); 
    } 
} 

Для настоящего класса Resource вам не нужно будет тестировать те методы, которые являются обертками вокруг собственных функций.Пример:

class Resource 
{ 
    // We don't need to unit test this, all it does is call a PHP function 
    public function write($data) 
    { 
     fwrite($this->handle, $data); 
    } 
} 

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

fsockopen($some_test_host, $port); 
fopen($some_test_data_file); 
// etc. 

Где $some_test_host содержит некоторые данные, которые вы можете связываться с для испытаний.