Github Actions + Laravel

Hace un tiempo les conté por acá cómo configurar Travis para poder crear un ambiente de integración continua en un proyecto de Laravel.

Y como saben también, hace un tiempo estuve jugando con Github Actions para correr los tests de un proyecto de Rails.

En este post les voy a contar como hacer para configurar Github Actions para ejecutar los tests de Laravel.

El Proyecto

El año pasado en Programación Web II – TUPAR, la materia en la que soy docente, hicimos un proyecto de ejemplo en Laravel, al cual le configuramos Github Actions, para generar un ambiente de Continuous Integration (CI).

El proyecto es muy sencillo, una To Do List, que tiene usuarios comunes y usuarios que son manager. Está todo en este repo de Github.

En cuanto a los tests, tiene bastantes Unit tests y Feature Tests. Tambien agregamos un test de Dusk (browser test) muy sencillo, solo entra y mira el titulo de la página. Este último es solo a efectos de poder ejecutar este tipo de test “visuales” en un ambiente de CI.

¿Qué vamos a configurar?

En principio queremos que corran los unit y feature tests. Luego vamos a crear otro workflow para que corran los tests de Dusk y por último crear uno que nos haga un análisis estático de código.

A su vez, vamos a configurar Codecov, para poder ver visualmente el coverage que tenemos.

Unit Tests y Feature Tests

Si miramos un poco el proyecto, vamos a ver que estamos usando docker-compose para tener varios containers:

  • App: Es donde tenemos PHP
  • Web: El servidor web Nginx
  • Database: La base de datos Postgres
  • Adminer: App para poder acceder a la base.
  • Selenium: Donde vamos a tener Chrome para ejecutar los Dusk Tests

Mirando esto lo primero que vamos a necesitar para correr Unit y Feature tests es una base de datos. Por lo tanto en nuestra acción vamos a necesitar un servicio de Postgres corriendo.

Configurar Postgres

Cuando agregamos el servicio de Postgres, tenemos que configurar el usuario, la password y el nombre de la base de datos. También mapeamos el puerto de Postgres al mismo puerto de la máquina host. De esta forma, donde en el container que genera Github, vamos a poder usar la base de datos.

Un dato importante acá es la variable de entorno DB_HOST. Usen 127.0.0.1, en lugar de localhost. (Alguna vez esto me trajo problemas)

Con respecto al servicio de Postgres se agrega una línea más, que sirve para saber si el servicio está listo para atender requests. No es completamente necesario, pero puede arreglarte algún que otro lío.

Steps

Luego, lo que tenemos que agregar son los steps que vamos a necesitar.

  1. Hacer checkout del codigo, este paso nos va a traer el código al container donde se van a ejecutar los tests.
  2. Crear el .env de Laravel para que la solución funcione. Aca es importante que tengamos un .env.example en nuestro proyecto con todo pre-configurado. Esto nos sirve tanto para usarlo en Github Actions o también para cualquier miembro de nuestro equipo pueda crearse un .env muy rapido y facil. Hay que recordar siempre de hacer los ajustes en ese archivo además de en nuestro .env, ya que este último archivo no se commitea en el repo.
  3. Instalar las dependencias usando composer
  4. Generar una key para que nuestra app funcione. Esta key es la que se usa para todo lo que tenga que ver con encripción en nuestra app.
  5. Actualizamos permisos de carpetas donde van a crearse algunos archivos, para que cualquier usuario pueda escribir.
  6. Ejecutamos los tests
  7. Subimos los resultados a Codecov.

name: Laravel
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
laravel-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:9.6
env:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: thisisasecretpassword
POSTGRES_DB: tasks_test
ports:
5432:5432
options: –health-cmd pg_isready –health-interval 10s –health-timeout 5s –health-retries 5
env:
DB_USERNAME: myuser
DB_PASSWORD: thisisasecretpassword
DB_HOST: 127.0.0.1
steps:
uses: actions/checkout@v2
name: Copy .env
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
name: Install Dependencies
run: composer install -q –no-ansi –no-interaction –no-scripts –no-progress –prefer-dist
name: Generate key
run: php artisan key:generate
name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
name: Execute tests (Unit and Feature tests) via PHPUnit
run: vendor/bin/phpunit –coverage-clover=coverage.xml
uses: codecov/codecov-action@v1
view raw laravel.yml hosted with ❤ by GitHub

Dusk Tests

GitHub - JoseVte/laravel-dusk-5.1

Para los Dusk Tests, qué son los que corren en el browser, vamos a tener que agregar algunas partes más.

En primer lugar, tenemos que agregar la variable de entorno APP_URL, para que cuando ejecutemos los tests, Dusk sepa dónde tiene que ir a abrir la app en el browser.

Steps

La primer diferencia con el workflow de la sección anterior es que como tenemos una configuración para ejecutar Dusk localmente, lo que tenemos que hacer es borrarla para que no interfiera con la configuración en Github Actions.

Luego los steps son prácticamente los mismos, se suman estos:

  • Ejecutar las migraciones, esto nos va a permitir que nuestra app funcione correctamente.
  • Actualizar el Plugin de Chrome, con este paso actualizamos el binario que vamos a usar de Chrome.
  • Le damos más permisos a la carpeta de binarios de Dusk para que se puedan ejecutar.
  • Levantamos el server de artisan para poder acceder a nuestra app. Redirigimos toda la salida a /dev/null para que no interfiera con el output de nuestros tests. Es muy importante el & del final, para poder ejecutar en paralelo los siguientes steps.
  • Ejecutamos Chrome, también agregamos el & al final.
  • Ejecutamos los tests de Dusk
name: Dusk Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
dusk-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:9.6
env:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: thisisasecretpassword
POSTGRES_DB: laravel
ports:
5432:5432
options: –health-cmd pg_isready –health-interval 10s –health-timeout 5s –health-retries 5
env:
DB_USERNAME: myuser
DB_PASSWORD: thisisasecretpassword
APP_URL: http://127.0.0.1:8000
steps:
uses: actions/checkout@v2
name: Copy .env
run: php -r "copy('.env.example', '.env');"
name: Remove .env.dusk.local
run: rm .env.dusk.local
name: Install Dependencies
run: composer install -q –no-ansi –no-interaction –no-scripts –no-progress –prefer-dist
name: Generate key
run: php artisan key:generate
name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
name: Run Migrations
run: php artisan migrate
name: Upgrade Chrome Driver
run: php artisan dusk:chrome-driver `/opt/google/chrome/chrome –version | cut -d " " -f3 | cut -d "." -f1`
name: Change permissions to dusk
run: chmod -R 0755 vendor/laravel/dusk/bin/
name: Run Laravel Server
run: php artisan serve > /dev/null 2>&1 &
name: Start Chrome Driver
run: ./vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 &
name: Execute dusk tests
run: php artisan dusk
view raw dusk.yml hosted with ❤ by GitHub

Análisis estático de código (Phan)

Por último, podemos crear otro workflow para chequear el estilo de código que tenemos.

Este es mucho más sencillo, porque al ser un análisis estático de código, no necesitamos tener la app corriendo. Solo necesitamos ejecutar Phan

name: Static Code Analysis
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
code-analysis:
runs-on: ubuntu-latest
steps:
uses: actions/checkout@v2
name: Install Dependencies
run: composer install -q –no-ansi –no-interaction –no-scripts –no-progress –prefer-dist
name: Run Phan
run: vendor/bin/phan –no-progress-bar –allow-polyfill-parser –output-mode checkstyle | vendor/bin/cs2pr –graceful-warnings –colorize | true
view raw static.yml hosted with ❤ by GitHub

Espero que les sirva y puedan usarlo en sus proyectos Laravel.

Travis CI + Laravel

En una de las materias que doy en la facultad (Programación Web II – TUPAR), estamos continuando un proyecto para los Bomberos de Trenque Lauquen.

Es un proyecto que comenzaron unos alumnos como trabajo de fin de carrera. Es una aplicación web para administrar el cuartel: materiales, vehículos, bomberos, asistencias, promociones de personal, etc. Si bien aún esta en Alpha, es un buen proyecto para aplicar muchísimos de los conceptos de Programación Web.

Este año decidimos enseñar Docker y Laravel, y uno de los conceptos claves que queremos que los alumnos aprendan es la calidad de código, hacer unit test e integration tests, probar un poco de TDD y tener la experiencia de usar un ambiente de Integración Continua.

La idea de este post es poder poner en forma de tutorial como configurar Travis-CI, para hacer el build de una aplicación escrita en Laravel, con tests de unidad y tests de integración.

Laravel y Testing

Laravel es un framework de PHP, según su página web: The PHP Framework For Web Artisans.

Tiene muchas features muy interesantes, como las migrations, el comando artisan, los templates de Blade, Eloquent, y más.

Uno de los puntos fuertes es lo bien integrado que está con la creación de test alrededor de la solución.

Obviamente que para los tests de unidad, usa PHPUnit, que ya está más que probado y es el standard de la industria. Pero para los tests de integración, usa Dusk, que es una herramienta que automatiza los browser tests y esta integrada con Artisan para correr.

Configurando de Travis

Lo primero que vamos a hacer es crear el archivo travis.yml, este es el archivo que le dice a Travis como tiene que crear el container para poder ejecutar los tests de nuestra aplicación.

.env.travis

No Description

Hasta llegar a este archivo terminado tuve unas cuantas luchas. En primer lugar seguí la explicación de la documentación oficial, sin ninguna suerte. Después de buscar mucho en Google, encontré a un post en Japones, en el cual @sutara_lumpur, explicaba como el había hecho para configurarlo y tenia un repo en Github con la solución andando.

Obviamente le agradecí como corresponde:

Ignacio Jonas on Twitter

@sutara_lumpur Thanks for your code on how to configure @travisci to run #lareveldusk.

Vamos a explicar un poco que dice este archivo, las partes que a mi me generaron más dudas, hay algunas partes que se auto-explican.

  • sudo: required : Para poder ejecutar Chrome necesitamos permisos de root en el container.
  • dist: trusty : Como necesitamos ejecutar Chrome, entonces nuestro container tiene que ser un Linux, en este caso Ubuntu Trusty.
  • services : Vamos a necesitar una base de datos para probar nuestra aplicación, en este caso Postgres.
  • addons:  Necesitamos que nuestro container tenga Chrome instalado, para correr los test de integración.
  • install: Ejecutamos composer install para que instale todas las dependencias.
  • before script:
    • Creamos la base de datos, el nombre tiene que coincidir con lo que luego pongamos en el .env.travis.
    • Creamos el archivo .env, copiando el archivo .env.travis con toda la configuración.
    • Generamos la key para que nuestra aplicación Laravel funcione.
    • Migramos la base de datos y además le cargamos los datos. En este punto podemos obviar el –seed, si es que vamos a regenerar la base por cada test que ejecute.
  • script
    • Ejecutamos Chrome y lo ponemos a escuchar en un puerto, en nuestro caso 9222, este puerto tiene que coincidir con el puerto que usemos en DuskTestCase.php
    • Levantamos el server para poder acceder a nuestra aplicación usando artisan, redirigir el output a /dev/null es opcional. De esta forma nos queda mas limpio el log de Travis.
    • Ejecutamos los tests de integración.
    • Ejecutamos los tests de unidad, y generamos el archivo de coverage
  • after_success
    • Creamos el reporte de coverage.

Ya tenemos nuestro archivo de configuración de Travis. Ahora creemos nuestro archivo .env.travis.

APP_ENV=testing
APP_DEBUG=true
APP_KEY=
APP_URL=http://localhost:8000
DB_CONNECTION=pgsql
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=bomberos_test
DB_USERNAME=postgres
DB_PASSWORD=
CACHE_DRIVER=array
SESSION_DRIVER=file
QUEUE_DRIVER=sync
TRAVIS=true
CHROME_HOST=http://localhost:9515

view raw
.env.travis
hosted with ❤ by GitHub

Este archivo, en nuestro caso, es casi una copia de el .env que tenemos en nuestra maquina local. La diferencia son:

  • DB_DATABASE: Ahora apunta a la base de datos que creamos en Travis.
  • DB_USER: Es el usuario de la base de datos del container de Travis.
  • DB_PASSWORD: Es vacío, porque el default password del usuario postgres en Travis es vacío.
  • TRAVIS: Es para indicar que estamos corriendo en Travis, esto me va a servir para hacer algunos cambios en el archivo DuskTestCase.php.
  • CHROME_HOST: Es la URL donde escucha Chrome.

Por último, hacemos un pequeño cambio en el archivo DuskTestCase.php

Este cambio nos ayuda a poder ejecutar los tests tanto en el ambiente de desarrollo como en Travis.

<?php
...
protected function driver()
{
$options_array = env('TRAVIS', false) ? ['–disable-gpu','–headless','–window-size=1100,600'] : [];
$options = (new ChromeOptions)->addArguments($options_array);
return RemoteWebDriver::create(
env('CHROME_HOST', 'http://selenium:4444/wd/hub'), DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
)
);
}
?>

view raw
DuskTestCase.php
hosted with ❤ by GitHub

En nuestro caso tenemos un ambiente de desarrollo en Docker, en el cual tenemos los siguientes containers:

  • app: Corremos PHP.
  • web: Corremos el Server (nginx).
  • database: Corremos Postgres.
  • adminer: Corremos adminer, para poder administrar la Base de Datos.
  • selenium: Corremos Chrome y dentro de este container corremos los test de integración.

Para correr los Tests de Integración, tenemos que usar la URL http://selenium:4444/wd/hub y si pasamos las options de headless, disable-gpu, etc. se hace muy lento, al punto de fallar por timeout.

Una vez que subimos estos cambios y configuramos Travis para que integre nuestro repositorios, la aplicación buildea.

 

Y tenemos el reporte de coverage, de lo que cubren los test de unidad. Como vemos es de un 3%, muy poco, pero ahora que lo podemos medir, lo podemos mejorar.

Espero que les sirva, nos estamos leyendo.