58 votes

Comment utiliser PHPUnit avec CodeIgniter?

J'ai lu et je lis des articles sur PHPUnit, SimpleTest, et d'autres infrastructures de Test Unitaire. Ils ont tous un son si grand! J'ai enfin eu le PHPUnit de travail avec Codeigniter grâce à https://bitbucket.org/kenjis/my-ciunit/overview

Maintenant, ma question est, comment puis-je l'utiliser?

Chaque tutoriel, je vois, il y a quelques abstrait, comme assertEquals(2, 1+1) ou de:

public function testSpeakWithParams()
{
    $hello = new SayHello('Marco');
    $this->assertEquals("Hello Marco!", $hello->speak());
}

C'est génial si j'avais une fonction qui serait sortie de ces prévisible de la chaîne. En général, mes apps prendre un tas de données à partir de la base de données, puis de les afficher dans une sorte de tableau. Alors, comment puis-je tester Codeigniter contrôleurs?

Je voudrais faire du Développement Piloté par les tests et j'ai lu le tutoriel sur PHPUnits site, mais encore une fois l'exemple semblent tellement abstrait. La plupart de mes codeigniter fonctions de l'affichage des données.

Est-il un livre ou un bon tutoriel avec une application pratique et des exemples de tests PHPUnit?

95voto

rdlowrey Points 15589

Il semble que vous comprendre la structure de base/syntaxe de la façon d'écrire des tests unitaires et de tests de CodeIgniter code ne doit pas être différent de test de non-code de CI, je tiens donc à vous concentrer sur vos préoccupations sous-jacentes/questions ...

J'ai eu les mêmes questions il y a pas longtemps avec PHPUnit. Comme quelqu'un sans formation formelle, j'ai trouvé que le Test Unitaire état d'esprit semble abstrait et artificiel au premier abord. Je pense que la principale raison pour cela-dans mon cas, et probablement le vôtre aussi, à partir de la question -- c'est que vous n'avez pas porté sur VRAIMENT de travail pour séparer les préoccupations dans votre code jusqu'à maintenant.

Le test affirmations semblent abstraites parce que la plupart de vos méthodes/fonctions susceptibles d'effectuer plusieurs tâches distinctes. La réussite du test mentalité nécessite un changement dans la façon dont vous pensez à votre code. Vous devriez arrêter de définir le succès en termes de "ça marche?" Au lieu de cela, vous devriez vous demander: "est-ce de travailler, il va bien jouer avec un autre code, il est conçu d'une manière qui le rend utile dans d'autres applications, et puis-je vérifier qu'il fonctionne?"

Par exemple, ci-dessous est un exemple simplifié de la façon dont vous avez écrit le code jusqu'à ce point:

function parse_remote_page_txt($type = 'index')
{
  $remote_file = ConfigSingleton::$config_remote_site . "$type.php";
  $local_file  = ConfigSingleton::$config_save_path;

  if ($txt = file_get_contents($remote_file)) {
    if ($values_i_want_to_save = preg_match('//', $text)) {
      if (file_exists($local_file)) {
        $fh = fopen($local_file, 'w+');
        fwrite($fh, $values_i_want_to_save);
        fclose($fh);
        return TRUE;
      } else {
        return FALSE;
      }
  } else {
    return FALSE;
  }  
}

Exactement ce qui se passe ici n'est pas important. Je vais essayer d'expliquer pourquoi ce code est difficile à tester:

  • C'est à l'aide d'un singleton classe de configuration pour générer des valeurs. La réussite de votre fonction dépend des valeurs de singleton, et comment pouvez-vous vérifier que cette fonction fonctionne correctement dans un isolement complet lorsque vous ne peut pas instancier la nouvelle configuration des objets avec des valeurs différentes? Une meilleure option pourrait être de passer votre fonction d'un $config argument qui consiste en une config objet ou un tableau dont les valeurs que vous pouvez contrôler. C'est largement appelé "l'Injection de Dépendance" et il y a des discussions de cette technique sur les interwebs.

  • Avis imbriquée IF des déclarations. Des moyens d'essai vous êtes couvrant tous les fichiers exécutables ligne avec une sorte de test. Lorsque vous imbriquez des instructions if vous êtes à la création de nouvelles branches de code qui nécessitent un nouveau test de chemin.

  • Enfin, vous voyez comment cette fonction, bien qu'il semble être de faire une chose (de l'analyse du contenu d'un fichier distant) est en fait l'exécution de plusieurs tâches? Si vous avec zèle séparer de vos préoccupations à votre code devient infiniment plus testable. Beaucoup plus testable façon de faire la même chose, ce serait ...


class RemoteParser() {
  protected $local_path;
  protected $remote_path;
  protected $config;

  /**
   * Class constructor -- forces injection of $config object
   * @param ConfigObj $config
   */
  public function __construct(ConfigObj $config) {
    $this->config = $config;
  }

  /**
   * Setter for local_path property
   * @param string $filename
   */
  public function set_local_path($filename) {
    $file = filter_var($filename);
    $this->local_path = $this->config->local_path . "/$file.html";
  }

  /**
   * Setter for remote_path property
   * @param string $filename
   */
  public function set_remote_path($filename) {
    $file = filter_var($filename);
    $this->remote_path = $this->config->remote_site . "/$file.html";
  }

  /**
   * Retrieve the remote source
   * @return string Remote source text
   */
  public function get_remote_path_src() {
    if ( ! $this->remote_path) {
      throw new Exception("you didn't set the remote file yet!");
    }
    if ( ! $this->local_path) {
      throw new Exception("you didn't set the local file yet!");
    }
    if ( ! $remote_src = file_get_contents($this->remote_path)) {
      throw new Exception("we had a problem getting the remote file!");
    }

    return $remote_src;
  }

  /**
   * Parse a source string for the values we want
   * @param string $src
   * @return mixed Values array on success or bool(FALSE) on failure
   */
  public function parse_remote_src($src='') {
    $src = filter_validate($src);
    if (stristr($src, 'value_we_want_to_find')) {
      return array('val1', 'val2');
    } else {
      return FALSE;
    }
  }

  /**
   * Getter for remote file path property
   * @return string Remote path
   */
  public function get_remote_path() {
    return $this->remote_path;
  }

  /**
   * Getter for local file path property
   * @return string Local path
   */
  public function get_local_path() {
    return $this->local_path;
  }
}

Comme vous pouvez le voir, chacune de ces méthodes de la classe gère une fonction particulière de la classe qui est facilement vérifiable. A la récupération de fichiers à distance de travail? A nous de trouver les valeurs qui nous ont été d'essayer de l'analyser? Etc. Tout d'un coup ceux résumé des affirmations semblent beaucoup plus utile.

À mon humble avis, le plus de vous plonger dans les tests le plus vous vous rendez compte qu'il est plus sur le code de bonne conception et sensible de l'architecture que de simplement s'assurer que les choses fonctionnent comme prévu. Et c'est là où les avantages de la programmation orientée objet vraiment commencer à briller. Vous pouvez tester le code de procédure de l'amende juste, mais pour un grand projet avec les parties interdépendantes test a un moyen de l'application d'une bonne conception. Je sais que ça peut être un troll appât pour certains de procédure gens, mais bon.

Plus vous test, plus vous allez trouver vous-même à l'écriture de code et de vous demander, "Vais-je être en mesure de tester cela?" Et si non, vous aurez probablement changer la structure.

Toutefois, le code n'a pas besoin d'être élémentaire pour être testées. Stubbing et se moquant vous permet de tester les opérations extérieures de la réussite ou de l'échec de ce qui est totalement hors de contrôle. Vous pouvez créer des montages de test opérations de base de données et n'importe quoi d'autre.

Plus je test, plus je me rends compte que si je vais avoir un moment difficile de tests de quelque chose, il est plus probable parce que j'ai une conception sous-jacente du problème. Si je redresser qu'il résulte généralement dans toutes les barres vertes dans mes résultats de test.

Enfin, voici quelques liens qui m'ont vraiment aidé à commencer à penser dans un test-d'une façon amicale. La première est une langue-dans-joue liste de choses à ne PAS faire si vous voulez écrire de code de tests. En fait, si vous parcourir l'ensemble de ce site vous trouverez beaucoup de trucs utiles qui aideront à vous mettre sur le chemin d'accès à 100% de couverture de code. Un autre article utile est cette discussion de l'injection de dépendance.

Bonne chance!

2voto

Jeune Points 1679

J'ai essayé, sans succès, d'utiliser PHPUnit avec Codeigniter. Par exemple, si je voulais tester mon implant Modèles, j'ai couru dans le problème de savoir comment je vais obtenir une instance de ce modèle, comme c'est en quelque sorte besoins de l'ensemble de la CI-cadre pour la charger. Considérez la façon dont vous chargez un modèle, par exemple:

$this->load->model("domain_model");

Le problème est que si vous regardez la super classe pour une méthode de chargement, vous ne le trouverez pas. Ce n'est pas aussi simple si vous faites des tests de la Plaine de Vieux Objets PHP, où vous pourrez vous moquer de votre dépendances facilement et de tester de nouvelles fonctionnalités.

Par conséquent, j'ai décidé de CI de tests Unitaires de la classe.

my apps grab a bunch of data from the database then display it in some sort of table.

Si vous testez vos contrôleurs vous êtes essentiellement de tester la logique d'entreprise (si vous avez) ainsi que la requête sql qui "attrape un tas de données" à partir de la base de données. C'est déjà le test d'intégration.

Le meilleur moyen est de tester le modèle CI d'abord de tester la procédure d'extraction de données --- ceci vous sera utile si vous avez un très compliquée, à la requête -- et puis, le contrôleur de côté tester la logique d'entreprise qui est appliquée aux données a attrapé par le Modèle CI. C'est une bonne pratique pour tester une seule chose à la fois. Alors, qu'allez-vous tester? La requête ou de la logique d'affaires?

Je suis en supposant que vous souhaitez tester la procédure d'extraction de données tout d'abord, les étapes sont

  1. Obtenir des données de test et de configuration de votre base de données, tables, etc.

  2. Disposer d'un mécanisme pour remplir la base de données avec des données de test ainsi que de la supprimer après le test. PHPUnit est l'extension de Base de données a une façon de le faire même si je ne sais pas si c'est pris en charge par le cadre que vous avez posté. Laissez-nous savoir.

  3. Écrivez votre test, pass.

Votre méthode de test pourrait ressembler à ceci:

// At this point database has already been populated
public function testGetSomethingFromDB() {
    $something_model = $this->load->model("domain_model");
    $results = $something_model->getSomethings();
    $this->assertEquals(array(
       "item1","item2"), $results);

}
// After test is run database is truncated. 

Juste au cas où vous souhaitez utiliser de CI de tests unitaires de la classe, voici le code modifié extrait d'un essai que j'ai écrit à l'aide de:

class User extends CI_Controller {
    function __construct() {
        parent::__construct(false);
        $this->load->model("user_model");
        $this->load->library("unit_test");
    }

public function testGetZone() {
            // POPULATE DATA FIRST
    $user1 = array(
        'user_no' => 11,
        'first_name' => 'First',
        'last_name' => 'User'
    );

    $this->db->insert('user',$user1);

            // run method
    $all = $this->user_model->get_all_users();
            // and test
    echo $this->unit->run(count($all),1);

            // DELETE
    $this->db->delete('user',array('user_no' => 11));

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X