web-dev-qa-db-fra.com

Atteindre 100% de couverture de code avec PHPUnit

Je suis en train de créer une suite de tests pour un projet et, bien que je réalise que l'obtention d'une couverture à 100% n'est pas la métrique doit être recherchée, le rapport de couverture de code contient un élément étrange. pour laquelle je voudrais des éclaircissements.

Voir la capture d'écran:

enter image description here

Comme la dernière ligne de la méthode en cours de test est une return, la dernière ligne (qui est juste un crochet de fermeture) apparaît comme n'étant jamais exécutée. Par conséquent, la méthode entière est marquée comme non exécutée dans la vue d'ensemble. (Soit ça, ou je ne lis pas le rapport correctement.)

La méthode complète:

static public function &getDomain($domain = null) {
    $domain = $domain ?: self::domain();

    if (! array_key_exists($domain, self::$domains)) {
        self::$domains[$domain] = new Config();
    }

    return self::$domains[$domain];
}

Y a-t-il une raison à cela ou est-ce un problème?

(Oui, j'ai lu Comment obtenir une couverture de code à 100% avec PHPUnit , cas différent bien que similaire.)

Modifier:

Après avoir parcouru le rapport, j'ai remarqué qu'il en était de même pour une déclaration switch ailleurs dans le code. Donc, ce comportement est au moins dans une certaine mesure cohérent, mais déroutant pour moi néanmoins.

Edit2:

J'utilise: PHPUnit 3.6.7, PHP 5.4.0RC5, XDebug 2.2.0-dev sur OS X

35
nikc.org

Tout d’abord: la couverture de code à 100% est une excellente mesure pours'efforcerpour. Ce n'est simplement pas toujours réalisable avec beaucoup d'effort et ce n'est pas toujours important de le faire :)

Le problème vient de xDebug disant à PHPUnit que cette ligne est exécutable mais non couverte.

Pour des cas simples, xDebug peut dire que la ligne n'est PAS accessible, vous obtiendrez ainsi une couverture de code à 100%.

Voir le exemple simple ci-dessous.


2ème mise à jour

Le problème est maintenant résolu xDebug bugtracker donc la construction d'une nouvelle version de xDebug résoudra ces problèmes :)

Mise à jour (voir ci-dessous pour des problèmes avec php 5.3.x)

Depuis que vous utilisez PHP 5.4 et la version DEV de xDebug, je les ai installés et testés. Je rencontre les mêmes problèmes que vous avec le même résultat que vous avez commenté.

Je ne suis pas sûr à 100% si le problème provient de php-code-coverage (le module phpunit) pour xDebug. Cela pourrait également être un problème avec xDebug dev.

J'ai déposé un bogue avec php-code-coverage et nous déterminerons l'origine du problème.


Pour les problèmes PHP 5.3.x:

Pour des cas plus complexes, celaPEUTéchouer.

Pour le code que vous avez montré, tout ce que je peux dire, c'est que "ça marche pour moi" ( échantillon complexe ci-dessous ).

Peut-être mettre à jour les versions xDebug et PHPUnit et réessayer.

Je l'ai vu échouer avec les versions actuelles, mais cela dépend parfois de l'apparence de toute la classe.

La suppression des opérateurs ?: et d’autres éléments comportant plusieurs instructions sur une seule ligne peut également aider.

À ma connaissance, xDebug procède actuellement à une refactorisation afin d'éviter davantage de cas de ce type. Une fois, xDebug veut pouvoir fournir une "couverture de relevé" et cela devrait régler beaucoup de cas. Pour l'instant il n'y a pas grand chose à faire ici

Bien que //@codeCoverageIgnoreStart et //@codeCoverageIgnoreEnd obtiennent cette ligne "couverte", cela semble vraiment moche et fait généralement plus de mal que de bien.

Pour un autre cas, voir la question et les réponses de:

what-to-do-when-project-coding-standards-conflicts-with-unit-test-code-coverage


Exemple simple:

<?php
class FooTest extends PHPUnit_Framework_TestCase {
    public function testBar() {
        $x = new Foo();
        $this->assertSame(1, $x->bar());
    }
}

<?php
class Foo {
    public function bar() {
        return 1;
    }
}

produit:

phpunit --coverage-text mep.php 
PHPUnit 3.6.7 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 3.50Mb

OK (1 test, 1 assertion)

Generating textual code coverage report, this may take a moment.

Code Coverage Report 
  2012-01-10 15:54:56

 Summary: 
  Classes: 100.00% (2/2)
  Methods: 100.00% (1/1)
  Lines:   100.00% (1/1)

Foo
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)

Exemple complexe:

<?php

require __DIR__ . '/foo.php';

class FooTest extends PHPUnit_Framework_TestCase {

    public function testBar() {
        $this->assertSame('b', Foo::getDomain('a'));
        $this->assertInstanceOf('Config', Foo::getDomain('foo'));
    }
}

<?php

class Foo {
    static $domains = array('a' => 'b');

    static public function &getDomain($domain = null) {
        $domain = $domain ?: self::domain();
        if (! array_key_exists($domain, self::$domains)) {
            self::$domains[$domain] = new Config();
        }
        return self::$domains[$domain];
    }
}

class Config {}

produit:

PHPUnit 3.6.7 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 3.50Mb

OK (1 test, 2 assertions)

Generating textual code coverage report, this may take a moment.

Code Coverage Report 
  2012-01-10 15:55:55

 Summary: 
  Classes: 100.00% (2/2)
  Methods: 100.00% (1/1)
  Lines:   100.00% (5/5)

Foo
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)
36
edorian

Une grande partie du problème réside dans l’insistance sur l’obtention d’une couverture à 100% des «lignes» lors de l’exécution. (Les gestionnaires aiment cette idée; il s'agit d'un modèle simple qu'ils peuvent comprendre). De nombreuses lignes ne sont pas "exécutables" (espaces, espaces entre les déclarations de fonction, commentaires, déclarations, "syntaxe pure", par exemple, la fermeture "}" d'une déclaration de commutateur ou de classe, ou d'instructions complexes réparties sur plusieurs lignes source).

Ce que vous voulez vraiment savoir, c’est "tous les exécutables codes sont-ils couverts?" Cette distinction semble stupide mais conduit à une solution. XDebug suit ce qui est exécuté, ainsi, par numéro de ligne et votre schéma basé sur XDebug rapporte ainsi des plages de lignes exécutées. Et vous obtenez les problèmes discutés dans ce fil, y compris les solutions klunky de devoir annoter le code avec des commentaires "ne me comptez pas", en plaçant "}" sur la même ligne que la dernière instruction exécutable, etc. Aucun programmeur n'est vraiment prêt à le faire et encore moins à le maintenir.

Si on définit le code exécutable comme ce code qui peut être appelé ou est contrôlé par un conditionnel (ce que les utilisateurs du compilateur appellent "blocs de base"), et le suivi de la couverture se fait de cette manière, la structure du code et les cas stupides tout simplement disparaître. Un outil de couverture de test de ce type collecte ce que l'on appelle "couverture de branche" et vous pouvez obtenir ou non une "couverture de branche" à 100% littéralement en exécutant tout le code exécutable. De plus, il prendra en compte les cas amusants dans lesquels vous avez une condition dans une ligne (en utilisant "x? Y: z") ou dans lesquels vous avez deux déclarations conventionnelles dans une ligne (par exemple,

 if  (...)  {   if  (...)  stmt1; else stmt2; stmt3 }

Étant donné que XDebug suit piste par ligne, je pense qu’il considère cela comme une seule déclaration et considère la couverture si le contrôle parvient à la ligne, alors qu’il ya 5 parties à tester.

Notre PHP outil de couverture de test implémente ces idées. En particulier, il comprend que le code suivant une instruction return n'est pas exécutable et vous indiquera que vous ne l'avez pas exécutée, s'il est non vide. Cela fait disparaître le problème initial du PO. Plus besoin de jouer à des jeux pour obtenir de "vrais" numéros de couverture.

Comme pour tous les choix, il y a parfois des inconvénients. Notre outil a un composant d'instrument de code qui ne fonctionne que sous Windows; Le code instrumenté PHP peut être exécuté n'importe où et le traitement/affichage est effectué par un programme Java indépendant de la plate-forme. Donc, cela pourrait être gênant pour le système OSX d’OP. L'instrumenteur fonctionne correctement sur les systèmes de fichiers compatibles NFS. Il peut donc exécuter l'instrumentateur sur un PC et instrumenter ses fichiers OSX.

Ce problème particulier a été soulevé par quelqu'un qui essayait de pousser ses chiffres de couverture vers le haut; le problème était artificiel IMHO et peut être guéri en contournant l'artificialité. Il existe un autre moyen d'augmenter vos chiffres sans écrire plus de tests: trouver et supprimer le code en double. Si vous supprimez les doublons, il y a moins de code à tester et tester un (non) copie dans les effets teste la (copie désormais non existante) afin qu'il soit plus facile d'obtenir des nombres plus élevés. Vous pouvez lisez plus à ce sujet ici.

4
Ira Baxter

En ce qui concerne votre problème de couverture de code d'instruction de commutateur, ajoutez simplement un cas "par défaut" qui ne fait rien et vous obtiendrez une couverture complète.

1
Lloyd Watkin

Voici ce qu'il faut faire pour obtenir une couverture de 100% des déclarations de commutateur:

Assurez-vous qu’au moins un test envoie un cas qui n’existe pas.

Donc, si vous avez:

switch ($name) {
    case 'terry':
        return 'blah';
    case 'lucky':
        return 'blahblah';
    case 'gerard':
        return 'blahblah';
}

assurez-vous qu'au moins un de vos tests envoie un nom qui n'est ni terry, ni lucky ni gerard.

0
Ibrahim Lawal