Cómo formatear y testear tu código usando git pre-commit hooks
No hay duda de que los beneficios de utilizar un sistema de control de versiones como Git son muchos. Sin embargo, por sí solo, Git solo hará un seguimiento de los commits que tú y tus compañeros de equipo hagaís en un repositorio, sin realizar ninguna comprobación sobre la calidad del código, como, por ejemplo: si sigue las reglas de linting, si está libre de errores o si pasa pruebas unitarias o de integración.
Para resolver estos problemas de forma sencilla, puedes usar Git hooks junto con herramientas que te permitan ejecutar herramientas de linting y pruebas antes o después de los eventos de Git, como crear un commit o subir código a un repositorio remoto.
¿Qué son los Git hooks?
Los Git hooks son scripts que Git ejecutará antes o después de algunas acciones que puedes realizar en tu repositorio, como crear un nuevo commit o subir código a un repositorio. Aunque estos son algunos de los hooks más comunes, puedes obtener más información sobre todos los demás en la documentación oficial.
La solución
Al usar Git hooks, podrás ejecutar herramientas de linting y probar el código que estás introduciendo con tu commit para verificar si sigue las reglas que tú y tu equipo habeís establecido. De esta manera, puedes asegurarte de que todos los commits siguen mejores prácticas, estén libres de fallos como errores de sintaxis o que pasen pruebas unitarias, de integración o de tipo end-to-end.
Si el código no pasa una de las reglas de los Git hooks, no se realizará el commit y podrás ver un informe de error de lo que se debe corregir para que se pueda realizar el commit. Por otro lado, si el código pasa todas las reglas especificadas, el commit se creará automáticamente.
Esto te permite fallar rápidamente sin tener que esperar a que las herramientas de CI (que pueden tardar mucho tiempo) fallen la compilación, lo que hará que tengas que cambiar mucho de contexto así afectando negativamente tu productividad. Puedes obtener más información sobre por qué esto es malo para tu productividad en este video de Paul Armstrong en @ReactEurope 2019. En cambio, en solo un par de minutos, o incluso menos, dependiendo de tu configuración, podrás ejecutar scripts y herramientas antes o después de los eventos de Git para ver si tu código está pasando los tests y las reglas de linting.
Así que vamos a ver cómo lograr estos objetivos mediante el uso de los Git
hooks, husky
y lint-staged
para poder ejecutar scripts que ejecutan
herramientas de linting y pruebas antes de realizar un commit o publicar
código a un repositorio remoto.
Instalación y configuración
Aunque puedes ejecutar Git hooks usando scripts de shell, es mucho más fácil
usarlos con herramientas como husky
. Por lo general, husky
se usa junto con
el paquete lint-staged
que te permite ejecutar hooks solo contra los
archivos que están en el staging area de Git, para evitar tener que ejecutar
las herramientas de linting con todo tu código o ejecutar todo el conjunto de
tests de tu repositorio cada vez que realizas un nuevo commit. Al usar el
paquete lint-staged
, podrás lintear y testear solo los archivos en el staging
area de de Git.
Instalando dependencias
husky
Primero, tendrás que instalar el paquete husky
, lo que te permitirá ejecutar
Git hooks como pre-commit
.
npm i --save-dev husky
lint-staged
Después, tendrás que instalar el paquete lint-staged
para poder ejecutar los
scripts solo contra los archivos del staging area de Git.
npm i --save-dev lint-staged
Linting
Configurando husky
Una vez has instalado husky
, tendrás que configurarlo. Para ello, yo he usado
un fichero .huskyrc.js
para incluir la configuración, pero
también soporta muchas otras maneras de hacerlo
(una de las más comunes es incluir las reglas y los scripts en el fichero
package.json
).
// .huskyrc.js
module.exports = {
hooks: {
"pre-commit": "lint-staged",
},
};
Esto ejecutará lint-staged
cuando el hook pre-commit
es ejecutado, lo cual,
a su vez, ejecutará los scripts que hayas incluido en la configuración del
paquete lint-staged
.
Configurando lint-staged
El siguiente paso es configurar lint-staged
para que tenga todos los scripts
que quieres ejecutar sobre los archivos del staging area de Git. Para ello,
tendrás que crear un fichero llamado .lintstagedrc.js
que contiene la
configuración de lint-staged
—también hay otras maneras de hacerlo, para saber
más, puedes leerlo en la
documentación oficial.
// .lintstagedrc.js
module.exports = {
"src/**/*.js": ["npm run lint:js"],
};
En este ejemplo, estoy usando lint-staged
para ejecutar el script de npm
lint:js
del fichero package.json
que ejecutará eslint
en todos los
ficheros que tengan una extensión de .js
, dentro de la carpeta src
. Si el
código dentro de estos ficheros no sigue alguna de las reglas de la
configuración de eslint
, se devolverá un error y eso hará que el commit no se
complete porque hay algún error.
// package.json
"scripts": {
"lint:js": "eslint . --ext .js"
}
Otra opción muy común es usar el flag --fix
de eslint
para que arregle los
errores que se puedan solucionar de forma automática.
// package.json
"scripts": {
"lint:js": "eslint . --ext .js",
"lint:js:fix": "npm run lint:js -- --fix"
}
Ahora, si intentas crear un commit que tenga algún cambio en un fichero que
tenga una extensión .js
dentro de la carpeta src
, el script npm lint:js
será ejecutado por lint-staged
cuando el hook pre-commit
es ejecutado por
husky
.
Testing
Para ir un paso más allá, también puedes ejecutar los tests de tu repositorio
con jest
cuando se ejecuta un Git hook.
Primero, tendrás que crear un script npm que ejecutará jest
con los siguientes
flags:
--bail
: detendrá el test suite entero cuando uno de los tests falle--findRelatedTests
: es útil usarlo con hooks tipopre-commit
para poder ejecutar solo los tests que afectan los ficheros que están en el staging area de Git
// package.json
"scripts": {
"lint:js": "eslint . --ext .js",
"lint:js:fix": "npm run lint:js -- --fix",
"test:related": "jest --bail --findRelatedTests"
}
Depués, tendrás que configurar lint-staged
para que ejecute también jest
antes de los Git hook que hayas elegido.
// .lintstagedrc.js
module.exports = {
"src/**/*.js": ["npm run lint:js", "npm run test:related"],
};
Ahora, lint-staged
ejecutará los scripts lint:js
y test:related
cada vez
antes de que hagas un commit. Si los tests pasan y no hay ningún error de
linting, el commit se llevará a cabo. De lo contrario, no se hará y verás un
mensaje de error con la información sobre lo que se tiene que arreglar en el
código.
Creando un nuevo commit
En este ejemplo estoy creando un commit de prueba para que veas la información
que se muestra cuando se ejecutan los scripts anteriores con lint-staged
y
husky
.
> git commit -m "hack hack hack"
husky > pre-commit (node v14.8.0)
✔ Preparing...
❯ Running tasks... ❯ Running tasks for src/**/*.js ⠼ npm run lint:js ◼ npm run test:related◼ Applying modifications...
◼ Cleaning up...
Todos los scripts pasan y el commit es creado
Una vez los scripts se han ejecutado y no hay ningún error, el commit se hará.
✔ Preparing...
✔ Running tasks...
✔ Applying modifications...
✔ Cleaning up...
[husky-post ccf9092] hack hack hack
1 file changed, 1 insertion(+)
Versión final de los archivos necesarios
package.json
// package.json
"scripts": {
"lint:js": "eslint . --ext .js",
"lint:js:fix": "npm run lint:js -- --fix",
"test:related": "jest --bail --findRelatedTests"
}
lint-staged
// .lintstagedrc.js
module.exports = {
"src/**/*.js": ["npm run lint:js", "npm run test:related"],
};
husky
// .huskyrc.js
module.exports = {
hooks: {
"pre-commit": "lint-staged",
},
};
Conclusiones
Ahora, cada vez que hagas un nuevo commit, podrás ver rápidamente si el código está libre de problemas de linting, errores y si pasa las pruebas unitarias o de integración. Al introducir estas herramientas en tus proyectos, puedes tener mucha más confianza en el código que publicas porque puedes ver más rápido si tiene algún problema que pueda afectar al repositorio sin tener que esperar a que las herramientas de CI te informen de los problemas.
Desde que he añadido los hooks de Git a mi flujo de trabajo, me siento mucho más cómodo escribiendo código y creando commits porque sé que tengo herramientas automatizadas que filtrarán y probarán los cambios que estoy introduciendo en cada commit.