Moquerie / entêtement des opérations FTP dans PHPUnit

je suis un converti relativement nouveau aux tests unitaires en général et je suis tombé sur une pierre d'achoppement ici:

Comment tester le code qui se connecte et effectue des opérations sur un serveur FTP distant en utilisant les fonctions ftp intégrées de PHP? Certains googling ont trouvé une option de moquerie rapide pour Java (MockFtpServer), mais rien de facilement disponible pour PHP.

j'ai un soupçon que la réponse pourrait être de créer une classe wrapper pour les fonctions ftp de PHP qui peut ensuite soyez assommé / moqué pour imiter des opérations ftp réussies/infructueuses, mais j'apprécierais vraiment la contribution de gens qui sont plus intelligents que moi!

s'il vous plaît noter que j'ai travaillé avec PHPUnit et besoin d'aide avec ce cadre en particulier.


selon la demande de @hakre le code simplifié que je veux tester ressemble à celui ci-dessous. Je demande essentiellement la meilleure façon de tester:

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;
}

MISE À JOUR / SOLUTION SOMMAIRE

Résumé Du Problème

Je ne savais pas comment tester des méthodes isolées qui nécessitaient la communication avec un serveur FTP distant. Comment es-tu supposé tester être capable de te connecter à une ressource extérieure sur laquelle tu n'as aucun contrôle, n'est-ce pas?

Résumé De La Solution

créer une classe adaptateur pour les opérations FTP, (méthodes: connect, ping, etc). Cet adaptateur classe est facilement écrasé de retour valeurs spécifiques lors du test d'un autre code qui utilise l'adaptateur pour effectuer des opérations FTP.

UPDATE 2

j'ai récemment rencontré un truc astucieux en utilisant des espaces de noms dans vos tests qui vous permet de "simuler" les fonctions intégrées de PHP. Alors que l'adaptateur était la bonne façon de procéder dans mon cas particulier, cela peut être utile à d'autres dans des situations similaires:

moquant des fonctions globales de php pour tester les unités

25
demandé sur rdlowrey 2011-11-30 01:42:04
la source

3 ответов

Deux approches qui viennent à l'esprit:

  1. créer deux adaptateurs pour votre classe FTP:

    1. le "vrai" qui utilise les fonctions ftp de PHP pour se connecter au serveur distant, etc.
    2. un "mock" qui ne se connecte à rien et ne renvoie que des données semées.

      la classe FTP' connect() méthode ressemble alors à ceci:

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

      l'adaptateur simulé pourrait ressembler à quelque chose comme ceci:

      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;
        }
      }
      

      dans votre test, vous allez ensuite ensemencer l'adaptateur avec un résultat et vérifier que connect() est appelé de manière appropriée:

      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.'
        );
      }
      

    Dans le test ci-dessus cas, setUp() injecte l'adaptateur simulé pour que les tests puissent invoquer la classe FTP'connect() sans déclencher une connexion FTP. Le test lance alors l'adaptateur avec un résultat qui sera si l'adaptateur est connect() méthode appelée avec les paramètres corrects.

    l'avantage de cette méthode est que vous pouvez personnaliser le comportement de l'objet mock, et il garde tout le code au même endroit si vous avez besoin d'utiliser le mock à travers plusieurs cas de test.

    les inconvénients de cette méthode sont que vous devez dupliquer beaucoup de fonctionnalités qui ont déjà été construites (voir approche #2), et sans doute vous avez maintenant introduit une autre classe que vous devez écrire des tests pour.

  2. une alternative est d'utiliser le framework moqueur de PHPUnit pour créer des objets dynamiques simulés dans votre test. Vous aurez toujours besoin d'injecter un adaptateur, mais vous pouvez le créer à la volée:

    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.'
      );
    }
    

    notez que le test ci-dessus se moque du carte pour la classe FTP,pas la classe FTP lui-même, car ce serait une chose assez stupide à faire.

    Cette approche présente des avantages par rapport à la précédente approche:

    • vous ne créez pas de nouvelles classes, et le framework mocking de PHPUnit a sa propre couverture de test, donc vous n'avez pas à écrire de tests pour la classe mock.
    • le test sert de documentation pour ce qui se passe 'sous le capot' (bien que certains puissent prétendre que ce n'est pas une bonne chose).

    cette approche comporte toutefois certains inconvénients:

    • C'est assez lent (en termes de performance) par rapport à l'approche précédente.
    • vous devez écrire beaucoup de code supplémentaire dans chaque test qui utilise la simulation (bien que vous puissiez modifier les opérations courantes pour atténuer une partie de cela).

    http://phpunit.de/manual/current/en/test-doubles.html#test-doubles.mock-objects pour plus d'informations.

10
répondu thaJeztah 2014-01-30 03:28:17
la source

votre approche semble être O-K. Il y a toujours des limites à ce que vous pouvez tester unité considérant éventuellement que vous serez à des fonctions de bas niveau qui interagissent directement avec les externes.

je vous recommande de baser votre FTP sur des adaptateurs que vous pouvez démonter, et ensuite vous pouvez couvrir le test de l'adaptateur réel via le test d'intégration.

4
répondu John Cartwright 2011-11-30 01:53:43
la source

alors qu'une option serait de se moquer du serveur FTP et de s'y connecter dans des tests (vous n'auriez besoin que de changer les détails du serveur ftp/configuration de connexion au serveur simulé et de ne pas changer de code) il y en a une autre: vous n'avez pas besoin d'unit-test des fonctions de PHP.

Ces fonctions ne sont pas des composants écrits par vous, alors vous ne devriez pas les tester.

sinon vous pourriez commencer à écrire le test pour if ou même des opérateurs comme + - mais vous ne le faites pas.

Pour en dire plus, il serait bon de voir votre code. Si vous avez un code de style procédural, il est difficile de simuler/stub/dupe chaque fonction FTP. En fait, ce n'est pas facilement possible avec PHP de toute façon.

mais si vous créez à la place un objet de connexion FTP qui enveloppe toutes ces fonctions, vous pouvez créer un dupe de test pour cet objet de connexion FTP. Cela nécessite toutefois un remaniement de votre code.

3
répondu hakre 2011-11-30 01:52:07
la source

Autres questions sur