Подтвердить что ты не робот

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

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

Как проверить код, который подключается и выполняет операции на удаленном FTP-сервере, используя встроенные функции ftp PHP? Некоторый 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. Хотя адаптер был правильным способом в моем конкретном случае, это может быть полезно для других в подобных ситуациях:

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

4b9b3361

Ответ 1

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

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

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

      Метод 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 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), и, возможно, вы теперь представили еще один класс, для которого вы должны писать тесты.

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

    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 mocking framework имеет собственное тестовое покрытие, поэтому вам не нужно писать тесты для класса mock.
    • Тест действует как документация для того, что происходит "под капотом" (хотя некоторые могут утверждать, что это не очень хорошо).

    Однако существуют некоторые недостатки этого подхода:

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

    Подробнее см. http://phpunit.de/manual/current/en/test-doubles.html#test-doubles.mock-objects.

Ответ 2

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

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

Ответ 3

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

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

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

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

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