Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Francisco Moreno committed Sep 21, 2020
0 parents commit 3594f28
Show file tree
Hide file tree
Showing 21 changed files with 6,896 additions and 0 deletions.
74 changes: 74 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless
*.pptx
80 changes: 80 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
// Use IntelliSense para saber los atributos posibles.
// Mantenga el puntero para ver las descripciones de los existentes atributos
// Para más información, visite: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Pact Server Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"10000",
"--colors",
"${workspaceFolder}/api/test/apiPactClientInsert.spec.js"
],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "Mocha Client Nock Test",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"${workspaceFolder}/client/test/ServiceClient.test.js"
],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "Mocha Server Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"${workspaceFolder}/api/test/unit.filmsController.test.js"
],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "IT Server Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"${workspaceFolder}/api/test/IT.test.js"
],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "Launch Server",
"program": "${workspaceFolder}\\api\\server.js"
},
{
"type": "node",
"request": "launch",
"name": "Launch Client",
"program": "${workspaceFolder}\\client\\index.js"
}
]
}
13 changes: 13 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"python.pythonPath": "C:\\Users\\Francisco Moreno\\AppData\\Local\\Programs\\Python\\Python37-32\\python.exe",
"python.formatting.provider": "black",
"spellright.language": [
"en",
"es"
],
"spellright.documentTypes": [
"markdown",
"latex",
"plaintext"
]
}
Binary file added Docs/ContractDrivenTesting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
203 changes: 203 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# Introducción

A día de hoy, es extraño encontrarnos con un sistema que se desarrolle de manera “monolítica”, cada vez es más habitual, por las ventajas que ello supone, dividirlo en componentes más pequeños que se comunican entre sí para cubrir las necesidades esperadas.
Esto hace que la funcionalidad no se encuentre concentrada en un único punto, si no que sea la colaboración de todas las partes la que dé sentido al sistema.
Lo recomendable es que cada una de estas partes tenga programadas unas pruebas unitarias que verifiquen que el componente se comporte correctamente de manera aislada. No obstante, como bien sabemos, que cada módulo funcione correctamente de manera individual no garantiza que el sistema se vaya comportar adecuadamente en conjunto.

En este punto, se podría pensar que unas pruebas de integración del sistema completo serían la solución, y en cierta medida lo son, no obstante,en muchas ocasiones, no resulta sencillo realizar este tipo de pruebas bien por su complejidad, bien porque “levantar” todo el sistema no es sencillo, porque haya partes que aún no estén terminadas, etc. En definitiva, hay muchos factores que pueden hacer que en un entorno orientado a microservicios realizar una batería completa de pruebas de integración resulte altamente complejo.

En este tipo de situaciones, es donde enfoques como el del testing orientado a contratos aportan un mayor valor. En esencia, se trata de establecer lo que espera recibir el consumidor ante unas determinadas peticiones y posteriormente verificar que efectivamente el productor está enviando las respuestas esperadas, tanto en forma como en datos. Estos contratos se generan mediante el mockeo del servidor, por tanto, se crean antes de comenzar el desarrollo del mismo, de manera que se garantiza que éste produce en todo momento las respuestas adecuadas.

Esto favorece la detección temprana de errores, ya que estas pruebas no necesitan tener toda la infraestructura del sistema levantas, únicamente el proveedor que estemos desarrollando y deseemos validar. Por tanto, en el momento que el contrato deje de cumplirse por parte de dicho proveedor seremos informados del error.

Existen varias librerías que dan soporte a este tipo de pruebas. En esta charla me centraré en el uso de PactJS.

# Contract Testing con PACT

[Pact](https://docs.pact.io) es un framework **open source** que facilita el testing de componentes basándose en contratos. Sus principales ventajas son:

- Open source
- Bien documentado
- Comunidad activa
- Soporte para los principales lenguajes (Ruby, .Net, Java, JS, PHP, etc.)
- Modelo "*Consumer Driven*"

## Consumer Driven Contract Testing

A la hora de realizar cualquier cambio en un sistema donde se sigue una arquitectura de proveedor/consumidor, el eslabón más débil de la cadena siempre será el consumidor y el que, principalmente, sufrirá los efectos de cualquier error.
En cualquier sistema orientado a servicios, se debe garantizar en todo momento que los consumidores de éstos, siguen operando con normalidad. Es por ello que se debe prestar especial atención a la hora de mantener la compatibilidad entre ambos sistemas. Esto es, principalmente, que el formato de las peticiones y respuestas sea el esperado en cada parte respectivamente.

Es por ello que en determinados tipos de soluciones tiene más sentido que sea el cliente quién "*tome la iniciativa*" a la hora definir las reglas de comunicación entre las partes. En la práctica. Este enfoque no implica que sea el cliente el que "dicte" las normas, en todo caso, el pacto debe surgir de una comunicación entre parte y plasmar los acuerdos que se hayan tomado.

## Cómo funciona

El framework **Pact** establece un workflow de trabajo determinado y aporta una serie de utilidades que permite llevarlo a cabo.
Los elementos principales que forman parte de este flujo son:

- Expectations (Interacciones)
- Mock Provider
- Pact file
- Mock Consumer
- Pact Broker (opcional)

De manera resumida, tal y como se muestra en el diagrama, el flujo podría resumirse en dos partes: "**Play & Record**" y "**Replay & Verify**"

El orden de ejecución sería el siguiente:

1. Definir las "interacciones" entre cliente y proveedor. Es decir, qué debe responder el proveedor ante peticiones concretas del consumidor.
2. Crear pruebas unitarias que "ejerciten" las interacciones anteriormente definidas.
3. Lanzar dichas pruebas unitarias mediante Pact
4. Pact, de manera automática, arrancará en la máquina un local un servicio que mockeará el comportamiento del proveedor, esto es que, ante las peticiones lanzadas por las pruebas unitarias de nuestro cliente devolverá las respuestas previamente establecidas, tal y como haría el servicio real en producción.
5. Esto nos permitirá verificar, antes de desplegar en producción, que nuestro cliente es compatible con las respuestas del proveedor.
6. Si las pruebas unitarias son correctas se generará un fichero JSON que contendrá el **PACTO**
7. Este fichero deberá ser compartido con el proveedor. *En cada proyecto y equipo de trabajo se deberá buscar la manera más adecuada de realizar esta acción. [Pact Broker](https://github.com/pact-foundation/pact_broker) puede ser una opción interesante por sus ventajas
8. Arrancamos el proveedor
9. En el lado del consumidor lanzamos el verificador de pactos indicando la ubicación del fichero con el pacto
10. Pact, levantará en local un mock del consumidor, éste lanzará contra nuestro proveedor las llamadas previamente definidas y comprobará las respuestas con las esperadas

![Contract Testing Diagram](./Docs/ContractDrivenTesting.png "Consumer Driven Contract Testing Diagram")

Trabajar con este framework aporta las siguientes ventajas:

- **Detección temprana de errores**: Verificamos la compatibilidad de nuestros sistemas antes de desplegar los sistemas
- **Escalable**: Estableceremos un pacto distinto por cada relación cliente-proveedor
- **Trabajo en paralelo**: Al mockear tanto el cliente como la del proveedor ambas partes podrán trabajar el paralelo, eliminando así las dependencias entre equipos.
- **Multi-tecnología**: Clientes y proveedores pueden estar desarrollados en diferentes lenguajes

## Ejemplos

El código mostrado a continuación sería el correspondiente a la implementación de PACT en Javascript.

### Instalación

Para instalar **PACT** en un entorno Node debemos ejecutar

```node
npm install @pact-foundation/pact --save-dev
```

Con esto, ya podremos hacer uso de PACT desde nuestro código. No obstante para poder *ejecutarlo*, necesitaremos que un *runner* de pruebas se encargue de lanzar las verificaciones pertinentes. En este ejemplo, y por simplicidad, utilizaremos la dupla "mocha + chai", aunque podría haber sido cualquier otra opción.

```node
npm install mocha chai --save-dev
```

**NOTA:** Esta configuración debería realizarse tanto en el lado del cliente como el del proveedor. En este caso, asumimos que ambas partes están desarrolladas sobre NodeJs. De no ser así, habría que utilizar la librería de PACT y runner de pruebas correspondientes en cada caso.
En el ejemplo de [GitHub](http://github.com/morvader/pactjs_testing) puede verse como realizar la verificación de un pacto en el caso de que el cliente se haya desarrollado en Python

### Configuración de PACT en cliente

**Propiedades generales**: Especificar nombre del consumidor y proveedor para facilitar la depuración así como el nombre y la ubicación del pacto generado

```javascript
const provider = new Pact({
consumer: 'Insert Films Client',
provider: 'Films Provider',
port: API_PORT,
log: path.resolve(process.cwd(), 'logs', 'pact.log'),
dir: path.resolve(process.cwd(), 'pacts'),
logLevel: LOG_LEVEL,
spec: 2
})
```

### Definición de interacciones

Especificamos la interacción y la prueba unitaria que la ejercita

```javascript
describe("Inserting films", () => {
before(() => {
filmService = new FilmsService(endPoint);
//Start mock service
return provider.setup();
})
after(() => {
// Generate pact file
return provider.finalize()
})
describe('When insert the meaning of life', () => {
describe('monty phyton film should be inserted', () => {
var pact_body = {"id": 42,
"Name": "Meaning of life",
"Description": "Comedy",
"Year": 1986}
before(() => {
//Interaccion
//Peticion y respuesta esperada
return provider.addInteraction({
state: 'Empty repository',
uponReceiving: 'Insert meaning of life',
withRequest: {
method: 'POST',
path: '/films/',
headers: {
'Content-Type': 'application/json'
},
body: pact_body
},
willRespondWith: {
status: 200,
headers: {
'Content-Type': 'application/json'
},
body: pact_body
}
})
})

//Prueba unitaria que ejercita la petición establecida
it('film is inserted', () => {
return filmService.insertFilm(42)
.then(response => {
expect(response).to.be.not.null;
expect(response.id).to.equal(42);
});
});
})
});
```
Para generar el pacto correspondiente pasaríamos a ejecutar las pruebas:
```node
mocha ./client/test/consumerPact.spec.js --timeout 10000
```
**Se añade un timeout por seguridad, ya que dedemos dejar tiempos al sistema a que levante el servidor mockeado*
Con esto tendríamos por un lado, el resultado de la pruebas que comprobarían que el cliente es compatible con la definición del contrato y por otro, el archivo JSon que especifica el contrato
### Verificar en el proveedor
- El verificador de Pact se encargará de lanzar las peticiones contra el servicio real y comprobar las respuestas con las especificadas.
- Indicar el endpoint del proveedor desplegado contra el que se lanzarán las peticiones
- Si fuese necesario, especificar la URL del servicio que se utilizará para realizar el setUp adecuado del sistema antes de la prueba.
- Indicar la ubicación, ya se ruta física o http, del fichero-pacto (generado previamente desde el cliente)
```javascript
let clienteNormal = {
provider:"Films Provider",
providerBaseUrl: 'http://localhost:3000',
providerStatesSetupUrl: 'http://localhost:3000/init',
pactUrls: [path.resolve(__dirname, '../../pacts/films_client-films_provider.json')]
};

new Verifier().verifyProvider(clienteNormal).then(() => {
console.log('success');
process.exit(0);
}).catch((error) => {
console.log('failed', error);
process.exit(1);
});
```
De la misma manera que en el lado del cliente, ejecutaríamos las pruebas en servidor:
```node
mocha ./api/test/apiPact.spec.js --timeout 10000
```
### Resultados
En la consola de ejecución veremos los resultado de la prueba, "success" en el caso de que todo haya ido bien o los mensajes de error correspondientes con las diferencias encontradas entre lo especificado en el pacto y las respuestas reales.
Loading

0 comments on commit 3594f28

Please sign in to comment.