Blog>

Roi Sánchez
09 Ene 2023

Cobertura de código con PHP usando AzureDevOps y SonarCloud

Tiempo de lectura 9 minutos
  • azure devops
  • buenas prácticas
  • CI/CD
  • clean code
  • php
  • phpunit
  • sonar
  • unit test
  • WordPress

Sumario Tutorial explicando los problemas que nos hemos encontrado para ejecutar las pruebas unitarias de PHPUnit en la máquina virtual de la pipeline de Azure DevOps, como solucionarlos y las configuraciones necesarias en SonarCloud.

Empecemos por que ya hemos conseguido configurar nuestros tests unitarios con PHP unit para ejecutarlos en local y además tenemos configurada la integración continua usando AzureDevops y Sonar; pues entonces es el momento de que con cada PR ejecutemos los test unitarios que hemos desarrollado, comprobemos que no hemos roto nada, nos aseguremos que nuestra cobertura de código general va en aumento, y por último que la cobertura de nuestro nuevo código cumple los parámetros de calidad mínimos que nos estamos autoexigiendo (en estos momentos un 80%).

Podemos pensar, yo de hecho lo hice, que una vez conseguido configurar phpUnit en local y conseguidos nuestros primeros tests satisfactorios ya teníamos prácticamente todo hecho, y que hacer correr nuestros tests en la pipeline de Azure DevOps iba a ser coser y cantar. Si se me complica mucho, en 1 hora lo tengo. Iluso de mí. Prefiero no contar ni recopilar cuantas horas me llevó esta configuración. Dejémoslo en que lo bastante como para que me mereciese la pena hacer este artículo por si en el futuro tenía que volver a configurarlo.

En lugar de contar qué problemas nos hemos ido encontrando y como los hemos ido solucionando, vamos a ir en otro orden, vamos a explicar los problemas que te encontrarás y como solucionarlos, pero explicado en el orden de solución de todos ellos.

Problemas, sí en plural….

Recuerda, que como dice el título, vamos a configurar la ejecución de phpunit con Azure DevOps y publicarlo en SonarCloud. Supongo que publicarlo en SonarQube será muy parecido.

Versión de PHP

El primer problema a solucionar es la versión de PHP. Cuando te creas una pipeline en Azure DevOps con Linux la máquina virtual que te va a crear es un Ubuntu-latest. Es decir, la última versión estable disponible de Ubuntu.

pool:
  vmImage: ubuntu-latest

El problema viene dado porque, como es lógico esta versión de Ubuntu tiene la última versión estable de PHP (8.x), mientras que WordPress usa la 7.4. Aunque podemos estar tentados de usar una versión anterior de Ubuntu, esta no es la mejor opción. Pierdes soporte de Microsoft ante cualquier problema y además puede ser que pasado x tiempo Azure borre la imagen y tu CI deje de funcionar. Encima, los warnings que te muestra no quedan bonitos.

Solución. Instalar la versión 7.4 en esta máquina virtual.

Esta será la primera lista de tareas de tu pipeline. Lo primero que necesitas es tener la versión correcta de php.

Para instalar la versión de PHP necesitamos 3 configuraciones:

Variable de versión de PHP

Debemos crearnos una variable con la versión de PHP ya que la vamos a traspasar a un montón de configuraciones y de esta forma si mañana tenemos que cambiar de versión no necesitamos cambiarlo en un montón de líneas de nuestro yml.

variables:
  phpVersion: 7.4

Instalar la versión 7.4 de PHP

No podemos hacer un install directamente de la versión 7.4 de PHP. Antes necesitamos añadir el repositorio desde donde nos lo podemos bajar, y una vez añadido podemos instalar esta versión.

Todo esto lo hacemos en un único paso de nuestro yml.

- script: |
      sudo add-apt-repository ppa:ondrej/php
      sudo apt-get update
      sudo apt -y install php$(phpVersion)
    displayName: "Install PHP version $(phpVersion)"

Nota: Fíjate que no le decimos la versión de PHP sino que referenciamos a la variable que hemos creado anteriormente: $(phpVersion).

Configurar la versión 7.4 de PHP como versión activa

Vale, ya hemos instalado la versión de PHP que necesitamos, pero ahora debemos decirle a la máquina que sea esta versión la que usamos. Esta es la parte del yml para ejecutar los scripts necesarios:

- script: |
      sudo update-alternatives --set php /usr/bin/php$(phpVersion)
      sudo update-alternatives --set phar /usr/bin/phar$(phpVersion)
      sudo update-alternatives --set phpdbg /usr/bin/phpdbg$(phpVersion)
      sudo update-alternatives --set php-cgi /usr/bin/php-cgi$(phpVersion)
      sudo update-alternatives --set phar.phar /usr/bin/phar.phar$(phpVersion)
      php -version
    displayName: "Use PHP version $(phpVersion)"

Nota: Fíjate que el último comando que ejecutamos es “php -version” de esta forma podemos ver en la ejecución de la pipeline que ha ido bien.

Instalar XDEBUG

Como nuestro objetivo es ejecutar los test de phpunit y conseguir el informe de cobertura de código necesitamos que la máquina ejecute xdebug, ya que el informe de cobertura nos lo dará esta extensión.

- script: |
      sudo apt-get install php$(phpVersion)-xdebug
    displayName: "Set XDEBUG mode"

Módulos necesarios para PHPUnit

Antes de explicar este punto vamos a hacer un pequeño inciso solo por si has llegado hasta este punto demasiado rápido. Recuerda que este artículo/tutorial está pensado para que configures tu pipeline de Azure Devops ejecutando tus tests de phpunit. Pero estos tests te deben funcionar en local previamente y debes tener el proyecto correctamente configurado con composer y PhpUnit. Si no es así, vuelve atrás y mírate este artículo.

Phpunit necesita el módulo ext (que se intala con el xml), el mbstring, el common y se recomienda el curl de lo contrario el proceso de instalación es muy lento (y piensa que tiene que instalar todo con cada ejecución de la pipeline).

Como en el resto de los pasos, aquí tienes el script:

- script: |
      sudo apt-get install php$(phpVersion)-curl
      sudo apt-get install php$(phpVersion)-common
      sudo apt-get install php$(phpVersion)-xml
      sudo apt-get install php$(phpVersion)-mbstring
      sudo service apache2 restart
    displayName: "Enable php extensions"

Instalación con composer

En Ubuntu latest ya viene una versión de composer, por tanto lo que tienes que hacer es decirle a composer que actualice las dependencias que tu proyecto ya debe tener referenciadas en el fichero composer.json.

- script: composer update --no-interaction --prefer-dist
    displayName: "composer update"

Ejecutar tus tests

¡BIEN! Después de todo esto para preparar tu máquina virtual ya puedes ejecutar los tests.

En este punto vas a lanzar un único comando en el que le dices varias cosas importantes:

 - script: php -d xdebug.mode=coverage ./vendor/bin/phpunit --coverage-clover=/home/vsts/work/1/s/coverage.xml -c ./phpunit.xml
    displayName: "Run phpunit tests"

phpunit.xml

Antes de explicar la configuración de phpunit.xml, creo que es necesario contar por encima que es eso del whitelist de phpUnit porque, al menos a mí, me ha costado encontrar información que lo explicara y me llevó su rato entenderlo. La whitelist define los directorios o ficheros sobre los que vas a hacer test, es decir qué ficheros de código fuente vas a probar con tus tests. Definir estos ficheros es fundamental para calcular la cobertura de código, ya que no vas a hacer pruebas unitarias sobre la totalidad del código, por ejemplo si pruebas un tema a medida no quieres que en el porcentaje de cobertura te cuente todo el código de WordPress y los plugins que estás usando.

Esta whitelist se configura definiendo los directorios que vas a probar (en nuestro caso el directorio ‘inc’ de nuestro tema), si hay algún fichero adicional a incluir (por ejemplo el fichero functions.php). También puedes definir algún directorio de exclusión, por ejemplo si dentro del directorio que vas a probar quieres excluir de la cobertura un directorio concreto lo puedes hacer con el exclude.

Por tanto, aunque en local no es necesario, para que el cálculo de cobertura Sonar lo pueda recuperar correctamente necesita que se le indique el whitetlist, ya que para cada fichero del que se indica cobertura en el informe, Sonar lo analiza.

Ejemplo de nuestro fichero de configuración

<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap = "./vendor/autoload.php">
    <testsuites>
        <testsuite name="Project Test Suite">
            <directory>./tests</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory suffix=".php">./src/wp-content/themes/xxxx</directory>
            <file>./src/wp-content/themes/xxx</file>
            <exclude>
                <directory suffix=".php">./src/wp-content/themes/xxx</directory>
            </exclude>
        </whitelist>
    </filter>
</phpunit>

Analizar y publicar en Sonar

A partir de aquí nuestra pipeline ejecuta los pasos corrientes de sonar de preparar, analizar y publicar.

- task: SonarCloudPrepare@1
    inputs:
      SonarCloud: "devanddel_sonar"
      organization: "devanddel"
      scannerMode: "CLI"
      configMode: "file"
      projectKey: "sonar.projectkey"
      projectName: "sonar.projectName"
      extraProperties: |
        sonar.projectKey=xxxxx
        sonar.projectName=xxxx

  - task: SonarCloudAnalyze@1

  - task: SonarCloudPublish@1
    inputs:
      pollingTimeoutSec: "300"

Vista completa de nuestro azure-pipelines.yml

Y finalmente nuestra configuración de la pipeline queda así:

# PHP
# Test and package your PHP project.
# Add steps that run tests, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/php

trigger:
  - develop

pool:
  vmImage: ubuntu-latest

variables:
  phpVersion: 7.4

steps:
  - script: |
      sudo add-apt-repository ppa:ondrej/php
      sudo apt-get update
      sudo apt -y install php$(phpVersion)
    displayName: "Install PHP version $(phpVersion)"

  - script: |
      sudo update-alternatives --set php /usr/bin/php$(phpVersion)
      sudo update-alternatives --set phar /usr/bin/phar$(phpVersion)
      sudo update-alternatives --set phpdbg /usr/bin/phpdbg$(phpVersion)
      sudo update-alternatives --set php-cgi /usr/bin/php-cgi$(phpVersion)
      sudo update-alternatives --set phar.phar /usr/bin/phar.phar$(phpVersion)
      php -version
    displayName: "Use PHP version $(phpVersion)"

  - script: |
      sudo apt-get install php$(phpVersion)-xdebug
    displayName: "Set XDEBUG mode"

  - script: |
      sudo apt-get install php$(phpVersion)-curl
      sudo apt-get install php$(phpVersion)-common
      sudo apt-get install php$(phpVersion)-xml
      sudo apt-get install php$(phpVersion)-mbstring
      sudo service apache2 restart
    displayName: "Enable php extensions"

  - script: composer update --no-interaction --prefer-dist
    displayName: "composer update"

  - script: php -d xdebug.mode=coverage ./vendor/bin/phpunit --coverage-clover=/home/vsts/work/1/s/coverage.xml -c ./phpunit.xml
    displayName: "Run phpunit tests"

  - task: SonarCloudPrepare@1
    inputs:
      SonarCloud: "devanddel_sonar"
      organization: "devanddel"
      scannerMode: "CLI"
      configMode: "file"
      projectKey: "sonar.projectkey"
      projectName: "sonar.projectName"
      extraProperties: |
        sonar.projectKey=xxxx
        sonar.projectName=xxxx

  - task: SonarCloudAnalyze@1

  - task: SonarCloudPublish@1
    inputs:
      pollingTimeoutSec: "300"

Configurar en SonarCloud el informe de cobertura

Una vez conseguido que la pipeline ejecute todo ahora debemos conseguir que SonarCloud nos cargue esta información. Para ello debemos indicarle donde hemos dejado el informe de cobertura y como se llama.

Para esto, dentro de la configuración de nuestro proyecto en SonarCloud debemos ir a General Settings > Language. Y dentro de esta pantalla, seleccionar PHP y en PHPUnit > Coverage Reports incluir el nombre de nuestro informe y la ruta. En nuestro caso:

Configuración de informe de cobertura

Resultado final

Y finalmente en nuestras PRs podremos ver la cobertura de nuestro código:

Resultado de informe de cobertura de una PR dentro de SonarCloud

Consejito final

Revisar bien el resultado de los pasos de la pipeline. Al configurar tu pipeline puede que veas que un paso en concreto se ha ejecutado como correcta pero no hace lo que espera. Entra en la ejecución del paso y mira con detenimiento la salida de la consola, puede que haya algún error en el medio del output pero el motor de pipeline no considere esto como erróneo y por tanto te marque la ejecución como correcta.

Autor

Roi Sánchez
Roi Sánchez

Desarrollador en dev&del

Capitán en Hello, World!

Capaz de gestionar un proyecto informático E2E (de principio a fin).

Los discos de vinilo y los tatuajes son dos de sus mayores pasiones.

¿Estás interesado?

Déjanos tus datos y contactaremos contigo lo antes posible