Mi viaje de prueba
El primer programa que escribí fue en un TI-83 en el instituto. No estaba tratando de escribir un código limpio. Sólo intentaba que funcionara. Desde entonces he aprendido el valor de las pruebas. Ha hecho mi vida mucho más fácil. Sin embargo, a medida que me volví más y más disciplinado en la escritura de pruebas, tuve el problema de que tenía MUCHAS pruebas que eran difíciles de mantener. En los últimos dos años he estado en un viaje para escribir pruebas de alta calidad que son más ligeras y más fáciles de mantener. Hoy compartiré cómo las funciones puras ayudan a que sus pruebas sean más limpias.
Funciones puras
Una función pura es aquella cuyo valor de retorno se basa únicamente en los argumentos de entrada. Además, no tiene ningún efecto secundario (normalmente E/S). Las operaciones comunes que no pueden ocurrir en una función pura son la lectura de una base de datos, la escritura en la consola o incluso la lectura de una variable global.
Estas funciones son fáciles de razonar. También son fáciles de probar porque no requieren ningún doble de prueba. Veamos un ejemplo sencillo.
funciónincremento(número){número de retorno+1;}
Y la prueba simple:
test($0027el incremento debe sumar uno al número$0027,()= > {expect(increment(1)).toEqual(2)})
Veamos una función que no es pura.
asyncfunctionincrement(){letnumber=awaitrepo.getNumberFromDatabase();number+=1;awaitrepo.setNumberInDatabase(number);returnnnumber;}
Esto no es puro porque obtiene datos de una base de datos. El resultado variará en función del estado externo. Para probar esto tenemos que espiar las funciones de nuestra base de datos y preprogramar su comportamiento.
describe($0027incremento$0027,()= >{test($0027cuando tiene éxito, debe añadir uno al número de la base de datos$0027,()=[;{constmockRepo=mock(repo)mockRepo.when($0027getNumberFromDatabase$0027).returns(1);mockRepo. when($0027setNumberInDatabase$0027).returns();awaitincrement();expect(mockRepo.setNumberInDatabase.calls[0][0]).toEqual(2);})test($0027when fetching the value from the database fails$0027,()={{// ...})})
Empujar I/O al borde de su aplicación
Su aplicación seguramente tendrá E/S y no hay nada malo en escribir pruebas de integración o usar dobles de prueba. El problema es que cuando hacemos que las funciones sean impuras, no tienen por qué serlo. Veamos un ejemplo.
functiongetAverageTransactionAmountForAccount(accountId){constsql=$0027SELECT * FROM transactions WHERE account_id = $1$0027;constresult=awaitdb. query(sql,[accountId]);constamounts=resultado.filas.map(row=§;fila.importe);constsum=importe.reducir((amount,sum)=[;amount+sum,0);returnsum/importes.longitud;}
Realmente estamos haciendo tres cosas aquí:
- Construyendo una consulta (podría ser pura)
- Ejecutando la consulta (I/O, impuro)
- Transformando el resultado (podría ser puro)
También debería probar su aplicación, ya sea con una prueba de unidad con dobles de prueba o una prueba de extremo a extremo. Sugiero lo último. Ya que todas las permutaciones están cubiertas por sus pruebas de unidad, no necesita cubrirlas todas de nuevo con su prueba de extremo a extremo. Normalmente lo mantengo al mínimo.
Separemos este código en estas tres secciones.
// ------------------------------// application.js// ------------------------------functiongetAverageTransactionAmountForAccount(accountId){constquery=buildSelectionTransactionsQuery(accountId);constresult=awaitdb.query(query);returngetAverageTransactionAmount(result.rows);}// ------------------------------// transforma. js// ------------------------------functionbuildSelectionTransactionsQuery(accountId){return{sql:$0027SELECT * FROM transactions WHERE account_id = $1$0027,params:[accountId]};}functiongetAverageTransactionAmount(transactionRows){constamounts=transactionRows. map(row==;row.amount);constsum=amounts.reduce((amount,sum)=;amount+sum,0);returnsum/amounts.length;}
- Construyendo la consulta: Esto está ahora contenido en buildSelectionTransactionsQuery y es puro. Aunque es pura, puede que quieras escribir una prueba de integración para asegurarte de que la consulta hace lo que esperas.
- Ejecutando la consulta (I/O): Esto está contenido en db.query.
- Transformando el resultado: Esto está ahora contenido en getAverageTransactionAmount. Contiene nuestro cálculo principal y ahora es mucho más fácil de probar.
Esto se vuelve especialmente útil con los cálculos más complicados que tienen más permutaciones.
Resumen
Hay muchas prácticas que te ayudarán a escribir código Javascript comprobable. Favoreciendo las funciones puras empujando E/S al límite ha sido una victoria fácil para mí. Pensar de esta manera es también un buen primer paso en la programación funcional.
Categorías: technicalTags: testing, javascript, functional programming