Navigation

Contactez-nous

Kitpages
17 rue de la Frise
38000 Grenoble
tel : 04 58 00 33 81

Par Elie Charra (twitter accounteliecharra) Dernière mise à jour : 01 septembre 2015

Behat PHP code coverage

Introduction

Ce petit tutoriel explique comment mettre en place le code coverage sur des tests Behat, dans notre cas sur une application Symfony 2 (et plus précisément la distribution api-platform)

Prérequis

Dans ce tutoriel nous partons du principe que vous avez déjà mis en place vos tests Behat et que vous souhaitez simplement profiter de la couverture de code.

Pour cela nous allons utiliser l'excellente librairie php-code-coverage de Sebastian Bergmann.

Veuillez toutefois noter que l'installation de xdebug est de dehors du périmètre de ce tutoriel, mais google est votre ami.

Installation via composer :

composer require phpunit/php-code-coverage

Mise en contexte

Pour ce tutorial nous utiliserons comme context de test une API Rest de gestion simplifié des Users.

Voici la configuration behat.yml qui nous utiliserons :

default:
  calls:
    error_reporting: 16383 # E_ALL & ~E_USER_DREPRECATED
  suites:
    default:
      contexts:
        - FeatureContext: { doctrine: "@doctrine" }
        - Behat\MinkExtension\Context\MinkContext
        - Sanpi\Behatch\Context\RestContext
        - Sanpi\Behatch\Context\JsonContext
  extensions:
    Behat\Symfony2Extension:
      kernel:
        env: "test"
        debug: "true"
    Behat\MinkExtension:
      base_url: "http://localhost/"
      sessions:
        default:
          symfony2: ~
    Sanpi\Behatch\Extension: ~

Et voici la feature Behat associé :

Feature: User
  I need to be able to retrieve, create, update and delete users trough the API.

  # "@createSchema" creates a temporary SQLite database for testing the API
  @createSchema
  Scenario: Create a user
    When I send a "POST" request to "/users" with body:
    """
    {"email": "test@example.com", "name": "Tester"}
    """
    Then the response status code should be 201
    And the response should be in JSON
    And the header "Content-Type" should be equal to "application/ld+json"
    And the JSON should be equal to:
    """
    {
      "@context": "/contexts/User",
      "@id": "/users/1",
      "@type": "http://schema.org/Person",
      "email": "test@example.com",
      "memberOf": null,
      "name": "Tester"
    }
    """

  Scenario: Create a user with invalid email
    When I send a "POST" request to "/users" with body:
    """
    {"email": "test@example", "name": "Tester"}
    """
    Then the response status code should be 400
    And the response should be in JSON
    And the header "Content-Type" should be equal to "application/ld+json"
    And the JSON node "root->hydra:description" should contain "email: This value is not a valid email address."

  Scenario: Get single user
    When I send a "GET" request to "/users/1"
    Then the response status code should be 200
    And the response should be in JSON
    And the header "Content-Type" should be equal to "application/ld+json"
    And the JSON should be equal to:
    """
    {
      "@context": "/contexts/User",
      "@id": "/users/1",
      "@type": "http://schema.org/Person",
      "email": "test@example.com",
      "memberOf": null,
      "name": "Tester"
    }
  """

  Scenario: Get single user not exist
    When I send a "GET" request to "/users/10"
    Then the response status code should be 404
    And the response should be in JSON
    And the header "Content-Type" should be equal to "application/ld+json"
    And the JSON node "root->hydra:description" should be equal to "Not Found"

  @dropSchema
  Scenario: Get user list
    When I send a "GET" request to "/users"
    Then the response status code should be 200
    And the response should be in JSON
    And the header "Content-Type" should be equal to "application/ld+json"
    And the JSON should be equal to:
    """
    {
      "@context": "/contexts/User",
      "@id": "/users",
      "@type": "hydra:PagedCollection",
      "hydra:totalItems": 1,
      "hydra:itemsPerPage": 30,
      "hydra:firstPage": "/users",
      "hydra:lastPage": "/users",
      "hydra:member": [
        {
          "@id": "/users/1",
          "@type": "http://schema.org/Person",
          "email": "test@example.com",
          "memberOf": null,
          "name": "Tester"
        }
      ]
    }
    """

Création du context Behat

Nous allons créer un contexte Behat dédié au coverage, pour cela il suffit de récupérer la classe CoverageContext ici : https://gist.github.com/eliecharra/9c8b3ba57998b50e14a6 et de la recopier dans votre projet parmis vos contextes.

<?php

use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;

/**
 * Created by PhpStorm.
 * User: elie
 * Date: 01/09/15
 * Time: 11:29
 */
class CoverageContext implements Context
{
    /**
     * @var PHP_CodeCoverage
     */
    private static $coverage;

    /** @BeforeSuite */
    public static function setup()
    {
        $filter = new PHP_CodeCoverage_Filter();
        $filter->addDirectoryToBlacklist(__DIR__ . "/../../vendor");
        $filter->addDirectoryToWhitelist(__DIR__ . "/../../src");
        self::$coverage = new PHP_CodeCoverage(null, $filter);
    }

    /** @AfterSuite */
    public static function tearDown()
    {
        $writer = new PHP_CodeCoverage_Report_HTML();
        $writer->process(self::$coverage, __DIR__ . "/../../app/logs/coverage");
    }

    private function getCoverageKeyFromScope(BeforeScenarioScope $scope)
    {
        $name = $scope->getFeature()->getTitle() . '::' . $scope->getScenario()->getTitle();
        return $name;
    }

    /**
     * @BeforeScenario
     */
    public function startCoverage(BeforeScenarioScope $scope)
    {
        self::$coverage->start($this->getCoverageKeyFromScope($scope));
    }

    /** @AfterScenario */
    public function stopCoverage()
    {
        self::$coverage->stop();
    }
}

Au besoin vous pouvez modifier les lignes suivantes pour coller à votre environnement.

Ces paramètres ont vocation à être variabilisés

// Modifier ces lignes pour ajouter / exclure des dossiers au coverage
$filter->addDirectoryToBlacklist(__DIR__ . "/../../vendor");
$filter->addDirectoryToWhitelist(__DIR__ . "/../../src");

//Modifier cette ligne pour changer le type de sortie générée
$writer = new PHP_CodeCoverage_Report_HTML();
// Modifier cette ligne pour rediriger la sortie du résultat du coverage
$writer->process(self::$coverage, __DIR__ . "/../../app/logs/coverage");

On va ensuite rajouter un profil Behat avec le contexte CoverageContext que l'on viens de créer.

Dans votre fichier de config behat.yml, créez un nouveau profil :

coverage:
  suites:
    default:
      contexts:
        - FeatureContext: { doctrine: "@doctrine" }
        - Behat\MinkExtension\Context\MinkContext
        - Sanpi\Behatch\Context\RestContext
        - Sanpi\Behatch\Context\JsonContext
        - CoverageContext # On rajoute CoverageContext aux contextes déjà existants

Tests

On peux maintenant lancer nos test soit avec, soit sans coverage en utilisant ces deux profils.

bin/behat

et

bin/behat --profile coverage

 

Commentaires

Ajouter un commentaire
Quelle version de phpunit/php-code-coverage
Quelle version de phpunit/php-code-coverage utilisez-vous pour cet article?

Je viens d'inclure `^4.0` et on dirait que le namespace a changé...
Comment différencier le --profile
Super article, mais il me reste une interrogation:

bin/behat --profile coverage

Comment est definit ce profile dans le fichier de configuration ?