Пересмешивание / заглушка операций FTP в PHPUnit - PullRequest
25 голосов
/ 30 ноября 2011

Я относительно недавно перешел на юнит-тестирование в целом, и тут я наткнулся на камень преткновения:

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

У меня есть подозрение, что ответом может быть создание класса-оболочки для PHP-функций PHPкоторый впоследствии может быть вставлен в тупик / подделан, чтобы имитировать успешные / неудачные операции 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(методы: подключение, пинг и т. д.).Этот класс адаптера затем легко заглушается, чтобы возвращать определенные значения при тестировании другого кода, который использует адаптер для выполнения операций FTP.

ОБНОВЛЕНИЕ 2

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

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

Ответы [ 3 ]

10 голосов
/ 30 ноября 2011

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

  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-соединения. Затем в тесте выполняется инициализация адаптера с результатом, который будет возвращен only , если метод адаптера 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 для получения дополнительной информации.

4 голосов
/ 30 ноября 2011

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

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

3 голосов
/ 30 ноября 2011

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

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

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

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

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

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