Capítulo 4. Separación de intereses

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

"Separación de preocupaciones"... es lo que quiero decir con "centrar la atención en algún aspecto": no significa ignorar los demás aspectos, sólo es hacer justicia al hecho de que, desde el punto de vista de este aspecto, el otro es irrelevante. Es tener una mentalidad única y múltiple simultáneamente.

Edsger Dijkstra, "Sobre el papel del pensamiento científico"

Nuestro código fuente ha crecido. Dependiendo del lenguaje, hay entre 50 y 75 líneas en un archivo fuente. Eso es más que una pantalla llena en muchos monitores de visualización, y desde luego más que una página impresa en este libro.

Antes de pasar a la siguiente función, dedicaremos algún tiempo a refactorizar nuestro código. Ese es el tema de este capítulo y de los tres siguientes.

Código de prueba y producción

Hasta ahora, hemos escrito dos tipos de código diferentes en .

  1. Código que resuelve nuestro problema de Dinero. Esto incluye Money y Portfolio y todo su comportamiento. A esto lo llamamos código de producción.

  2. Código que verifica que el problema se ha resuelto correctamente. Esto incluye todas las pruebas y el código necesario para soportar dichas pruebas. A esto lo llamamos código de prueba.

Hay similitudes entre los dos tipos de código: están en el mismo lenguaje, los escribimos en rápida sucesión (a través del ya familiar ciclo rojo-verde-refactor), y confirmamos ambos en nuestro repositorio de código. Sin embargo, hay algunas diferencias clave entre los dos tipos de código.

Dependencia unidireccional

El código de prueba tiene que depender del código de producción, al menos en las partes del código de producción que prueba. Sin embargo, no debe haber dependencias en la otra dirección.

Actualmente, todo nuestro código para cada idioma está en un solo archivo, como se muestra en la Figura 4-1. Así que no es fácil garantizar que no haya dependencias accidentales del código de producción al código de prueba. Existe una dependencia implícita del código de prueba al código de producción. Esto tiene un par de implicaciones:

  1. Al escribir código, tenemos que tener cuidado de no utilizar accidentalmente ningún código de prueba en nuestro código de producción.

  2. Al leer código, tenemos que reconocer los patrones de uso y también fijarnos en los patrones ausentes, es decir, en el hecho de que el código de producción no puede llamar a ningún código de prueba.

One module named 'Everything', containing both test code and production code. The test code implicitly depends on the production code.
Figura 4-1. Cuando el código de prueba y el código de producción están en el mismo módulo, la dependencia del primero con respecto al segundo es implícita
Importante

El código de prueba depende del código de producción; sin embargo, no debe haber dependencia en el otro sentido.

Si el código de producción depende del código de prueba, ¿cuáles son los posibles malos resultados? En casos especialmente malos, puede llevarnos a un camino en el que la ruta del código que se prueba es "prístina", mientras que las rutas que no se prueban están plagadas de errores. La Figura 4-2 muestra una parte del pseudocódigo de la unidad de control del motor de un coche. El código funciona de forma diferente si el motor se está probando para cumplir la normativa de emisiones que cuando el motor se utiliza "en el mundo real".

Pseudocode showing that when isEmissionsTest variable is true, the engine's control unit is set to LOW_EMISSIONS_MODE. Otherwise, it is set to HIGH_PERFORMANCE_MODE.
Figura 4-2. La dependencia accidental del código de producción del código de prueba puede crear rutas de código de producción que se comporten de forma diferente y no probada

Si eres escéptico de que un caso tan flagrante de "compórtate lo mejor que puedas para los exámenes" pueda ocurrir en la realidad, te animamos a que leas sobre el escándalo de las emisiones de Volkswagen, del que se ha extraído el pseudocódigo de la Figura 4-2.1

Tener una dependencia unidireccional -en la que el código de producción no dependa en modo alguno del código de prueba y, por tanto, no sea susceptible de comportarse de forma diferente cuando se somete a prueba- es vital para garantizar que no se cuelen defectos de esta naturaleza (ya sean accidentales o malintencionados).

Inyección de dependencia

La inyección de dependencia es una práctica para separar la creación de un objeto de su uso. Aumenta la cohesión del código y reduce su acoplamiento.2 La inyección de dependencias requiere que distintas unidades de código (clases y métodos) sean independientes entre sí. Separar el código de prueba del de producción es un requisito previo importante para facilitar la inyección de dependencias.

Tendremos más que decir sobre la inyección de dependencias en el Capítulo 11, donde la utilizaremos para mejorar el diseño de nuestro código.

Empaquetado e Implementación

Cuando el código de la aplicación se empaqueta para su implementación, el código de prueba casi siempre se empaqueta por separado del código de producción. Esto permite desplegar el código de producción y el de prueba de forma independiente. A menudo, sólo se despliega el código de producción en determinados entornos "superiores", como el de producción. Esto se muestra en la Figura 4-3.

A developer commits code to a version control system. A CI build server builds production and test packages as separate artifacts. Both production and test artifacts are deployed on any non-production environment. However, only the production artifacts are deployed on the production environment.
Figura 4-3. El código de prueba debe empaquetarse por separado del código de producción para que puedan desplegarse de forma independiente a través de la canalización CI/CD.

Describiremos la implementación con más detalle en el capítulo 13, cuando construyamos una canalización de integración continua para nuestro código.

Modularización

Lo primero que haremos es separar el código de prueba del código de producción. Para ello tendremos que resolver el problema de incluir, importar o requerir el código de producción en el código de prueba. Es fundamental que ésta sea siempre una dependencia unidireccional, como se muestra en la Figura 4-4.

When test code is packaged in a test module and production code is in a production module, there needs to be an explicit, unidirectional dependency from the test module to the production module.
Figura 4-4. Sólo el código de prueba debe depender del código de producción, no al revés

En la práctica, esto significa que el código debe modularse siguiendo estas líneas:

  1. El código de prueba y el de producción deben estar en archivos fuente separados. Esto nos permite leer, editar y centrarnos en el código de prueba o de producción de forma independiente.

  2. El código debe utilizar espacios de nombres para identificar claramente qué entidades van juntas. Un espacio de nombres puede denominarse "módulo" o "paquete", según el lenguaje.

  3. En la medida de lo posible, debe haber una directiva de código explícita -import, require, o similar, según el lenguaje - para indicar que un módulo depende de otro. Esto garantiza que podamos especificar explícitamente la dependencia que se muestra en la Figura 4-1.

También buscaremos oportunidades para hacer que el código sea más autodescriptivo. Esto incluiría renombrar y reordenar entidades, métodos y variables para reflejar mejor su intención.

Eliminar redundancia

Lo segundo que haremos será eliminar la redundancia de nuestras pruebas.

Hace tiempo que tenemos dos pruebas de multiplicación: una para euros y otra para dólares. Prueban la misma funcionalidad. En cambio, sólo tenemos una prueba para la división. ¿Deberíamos mantener las dos pruebas de multiplicación?

Rara vez hay una respuesta irrefutable de "sí" o "no" a esto. Podríamos argumentar que las dos pruebas nos protegen de la codificación inadvertida de la moneda en el código que realiza la multiplicación, aunque ese argumento se vería debilitado por el hecho de que tenemos una prueba para la división y ahí podría surgir un error similar de codificación de moneda.

Para que nuestra toma de decisiones sea más objetiva, he aquí una lista de control:

  1. ¿Tendríamos la misma cobertura de código si suprimiéramos una prueba? La cobertura de líneas es una medida del número de líneas de código que se ejecutan al ejecutar una prueba. En nuestro caso, no habría pérdida de cobertura si suprimiéramos alguna de las pruebas de multiplicación.

  2. ¿Una de las pruebas verifica un caso de perímetro significativo? Si, por ejemplo, estuviéramos multiplicando un número realmente grande en una de nuestras pruebas y nuestro objetivo fuera asegurarnos de que no se produce desbordamiento/desbordamiento en diferentes CPU y sistemas operativos, podríamos justificar el mantenimiento de ambas pruebas. Sin embargo, tampoco es el caso de nuestras dos pruebas de multiplicación.

  3. ¿Aportan las distintas pruebas un valor único como documentación viva? Por ejemplo, si utilizáramos símbolos monetarios de más allá del conjunto de caracteres alfanuméricos ($, €, ₩), podríamos decir que mostrar estos símbolos monetarios dispares aporta un valor adicional como documentación. Sin embargo, actualmente utilizamos letras extraídas de las mismas 26 letras del alfabeto inglés (USD, EUR, KRW) para nuestras monedas, por lo que la variación entre monedas aporta un valor mínimo como documentación.

Consejo

La coberturade líneas (o sentencias), la cobertura de ramas y la cobertura de bucles son tres métricas diferentes que miden qué parte de un determinado cuerpo de código se ha probado.

Dónde estamos

En este capítulo, hemos revisado la importancia de la separación de intereses y la eliminación de la redundancia. Éstos son los dos objetivos que centrarán nuestra atención en los tres capítulos siguientes.

Actualicemos nuestra lista de funciones para reflejarlo:

5 USD × 2 = 10 USD

10 EUR × 2 = 20 EUR

4002 KRW / 4 = 1000,5 KRW

5 USD + 10 USD = 15 USD

Separar el código de prueba del código de producción

Eliminar pruebas redundantes

5 USD + 10 EUR = 17 USD

1 USD + 1100 KRW = 2200 KRW

Nuestros objetivos están claros. Los pasos para alcanzarlos -especialmente el primer objetivo de separación de preocupaciones- variarán significativamente de un lenguaje a otro. Por ello, la puesta en práctica se ha separado en los tres capítulos siguientes:

Lee estos capítulos en el orden que tenga más sentido para ti. Consulta "Cómo leer este libro" para orientarte.

1 Sobre el escándalo "dieselgate" de Volkswagen, Felix Domke ha trabajado mucho. Es coautor de un libro blanco. También pronunció un discurso en la conferencia del Chaos Computer Club.

2 La cohesión y el acoplamiento se describen con más detalle en el capítulo 14.

Get Aprender el desarrollo basado en pruebas now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.