Отказывание / прерывание операций FTP в PHPUnit

Я относительно новый конвертер для модульного тестирования в целом, и я столкнулся с камнем преткновения здесь:

Как проверить код, который подключается и выполняет операции на удаленном FTP-сервере, используя встроенные в PHP функции ftp? Некоторый googling оказался быстрым издевательским вариантом для Java ( MockFtpServer ), но ничего не доступно для PHP.

У меня есть подозрение, что ответ может заключаться в создании класса-оболочки для PHP-функций ftp, которые впоследствии могут быть заглушены / изделены, чтобы имитировать успешные / неудачные операции ftp, но я бы очень признателен за вклад от людей, которые умнее меня!

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


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

public function connect($conn_name, $opt=array()) { if ($this->ping($conn_name)) { return TRUE; } $r = FALSE; try { if ($this->conns[$conn_name] = ftp_connect($opt['host'])) { ftp_login($this->conns[$conn_name], $opt['user'], $opt['pass']); } $r = TRUE; } catch(FtpException $e) { // there was a problem with the ftp operation and the // custom error handler threw an exception } return $r; } 

ОБНОВЛЕНИЕ / РЕШЕНИЕ РЕЗЮМЕ

Резюме проблемы

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

Резюме решения

Создайте класс адаптера для операций FTP (методы: connect, ping и т. Д.). Этот класс адаптера затем легко заглушается, чтобы возвращать определенные значения при тестировании другого кода, который использует адаптер для выполнения операций FTP.

ОБНОВЛЕНИЕ 2

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

Mocking php глобальные функции для модульного тестирования

Два подхода, которые приходят на ум:

  1. Создайте два адаптера для вашего класса FTP:

    1. «Реальный», который использует PHP-функции ftp для подключения к удаленному серверу и т. Д.
    2. «Макет», который фактически не подключается к чему-либо и возвращает только семенные данные.

      Метод FTP connect() теперь выглядит следующим образом:

       public function connect($name, $opt=array()) { return $this->getAdapter()->connect($name, $opt); } 

      Макет адаптера может выглядеть примерно так:

       class FTPMockAdapter implements IFTPAdapter { protected $_seeded = array(); public function connect($name, $opt=array()) { return $this->_seeded['connect'][serialize(compact('name', 'opt'))]; } public function seed($data, $method, $opt) { $this->_seeded[$method][serialize($opt)] = $data; } } 

      В своем тесте вы затем высеваете адаптер с результатом и убедитесь, что connect() получает вызов соответствующим образом:

       public function setUp( ) { $this->_adapter = new FTPMockAdapter(); $this->_fixture->setAdapter($this->_adapter); } /** This test is worthless for testing the FTP class, as it * basically only tests the mock adapter, but hopefully * it at least illustrates the idea at work. */ public function testConnect( ) { $name = '...'; $opt = array(...); $success = true // Seed the connection response to the adapter. $this->_adapter->seed($success, 'connect', compact('name', 'opt')); // Invoke the fixture's connect() method and make sure it invokes the // adapter properly. $this->assertEquals($success, $this->_fixture->connect($name, $opt), 'Expected connect() to connect to correct server.' ); } 

    В приведенном выше тестовом примере setUp() вводит макет адаптера, чтобы тесты могли вызвать метод connect() класса FTP, фактически не инициировав FTP-соединение. Затем тест семя адаптера получает результат, который будет возвращен только в том случае, если метод connect() адаптера был вызван с правильными параметрами.

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

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

  2. Альтернативой является использование фальшивой структуры PHPUnit для создания динамических макетных объектов в вашем тесте. Вам все равно придется вставлять адаптер, но вы можете его создать «на лету»:

     public function setUp( ) { $this->_adapter = $this->getMock('FTPAdapter'); $this->_fixture->setAdapter($this->_adapter); } public function testConnect( ) { $name = '...'; $opt = array(...); $this->_adapter ->expects($this->once()) ->method('connect') ->with($this->equalTo($name), $this->equalTo($opt)) ->will($this->returnValue(true)); $this->assertTrue($this->_fixture->connect($name, $opt), 'Expected connect() to connect to correct server.' ); } 

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

    Такой подход имеет преимущества перед предыдущим подходом:

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

    Однако есть некоторые недостатки этого подхода:

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

    См. http://phpunit.de/manual/current/en/test-doubles.html#test-doubles.mock-objects для получения дополнительной информации.

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

Я бы рекомендовал вам настроить свой FTP на адаптерах, которые вы можете издеваться, а затем вы можете покрыть фактическое тестирование адаптера с помощью тестирования интеграции.

Хотя одним из вариантов было бы издеваться над FTP-сервером и подключаться к нему в тестах (вам нужно было бы только изменить конфигурацию ftp-сервера / конфигурацию подключения к макету сервера и не изменять какой-либо код), есть еще один: вы не необходимо выполнить модульные функции PHP.

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

В противном случае вы можете начать писать тест, if или даже такие операторы, как + но вы этого не делаете.

Чтобы сказать больше, было бы хорошо видеть ваш код. Если у вас есть процедурный код стиля, сложно выполнить mock / stub / dupe каждую функцию FTP. На самом деле это не так легко с PHP.

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