3. PRUEBAS UNITARIAS
Test que validan el comportamiento y
funcionamiento de partes de código
específicas y son aplicadas durante la
fase de Desarrollo de software.
CHAPTER 9
5. Pruebas automatizadas
Leyes del TDD:
1. No crear código de producción hasta crear una prueba unitaria que
presente fallos.
2. No crear más de una prueba de unidad fallida.
3. Crear código de producción necesario para superar la prueba de fallo.
Impulsadas por el desarrollo guiado por pruebas y el movimiento ágil.
El TDD implica escribir el código de las pruebas previamente al código de producción.
6. Código en evolución encargado
de la eficiencia y funcionalidad
del código de producción.
Alto nivel de calidad e
importancia para asegurar
garantía del producto.
Aportan flexibilidad, facilitan el
mantenimiento y posibilidad de
reutilización.
Eliminan riesgos de errores de
difícil detección en código de
producción, posibilitan mejora
de planificación y arquitectura.
Pruebas limpias
7. ¿Qué hace que las pruebas sean
limpias?
Claridad Simplicidad Densidad de
expresión
13. Diferencias entre códigos de prueba
9-1 SerializedPage
ResponderTest.java
Difícil de entender y
puede mejorarse
Existe código duplicado
que interfiere con
expresividad de prueba.
Ejecución, emisión,
recopilación y creación de
URL demasiado compleja.
9-2 SerializedPage
ResponderTest.java
(refactorizado)
Cumple la misma
función de forma clara y
descriptiva
Patrón BUILD-OPERATE-
CHECK en la estructura del
código
Utilizan tipos de datos y funciones
necesarias, mayor legibilidad y
comprensión para los lectores.
14. ● En lugar de usar API para manipular el sistema, utilizar funciones y
utilidades que usan las API, para facilitar la lectura y escritura de las
pruebas.
● API: lenguaje de pruebas que los programadores usan parar crea
los test y facilitan el entendimiento del lector, evoluciona a través de
cambios continuos en el código.
● Código de la API de pruebas debe ser más sencillo, breve y
expresivo.
● Diferencia de estándares entre código de pruebas y producción en
términos de eficacia.
Lenguaje de prueba específico del dominio
15. Ejemplos:
● Listado 9-3 EnvironmentControllerTest.java (Prototipo de sistema de control
medioambiental)
● Esta prueba verifica que la alarma de baja temperatura, el calentador y el ventilador se
encienden cuando la temperatura sea demasiado fría.
@Test
public void turnOnLoTempAlarmAtThreashold() throws Exception
{
hw.setTemp(WAY_TOO_COLD);
controller.tic(); assertTrue(hw.heaterState());
assertTrue(hw.blowerState());
assertFalse(hw.coolerState());
assertFalse(hw.hiTempAlarm());
assertTrue(hw.loTempAlarm());
}
16. @Test
public void turnOnLoTempAlarmAtThreshold() throws Exception
{
wayTooCold();
assertEquals("HBchL", hw.getState());
}
● Función tic reemplazada por wayTooCold, en asserEqual se indica el estado activo e inactivo de los
elementos en el siguiente orden: {calentador, ventilador, enfriador, alarma de alta temperatura,
alarma de baja temperatura}.
● Conociendo el significado de las letras y el código podemos interpretar y comprender el resultado
con mayor facilidad que en la prueba pasada.
Listado 9-4 EnvironmentControllerTest.java (refactorizado)
17. ● La función getState se desarrolla en el código 9.6 por lo que la codificación no es muy eficaz y en
lugar de esta función se pudo utilizar StringBuffer para cambiar las cadenas de estado.
@Test
public void turnOnCoolerAndBlowerIfTooHot() throws Exception
{
tooHot();
assertEquals("hBChl", hw.getState());
}
@Test
public void turnOnHeaterAndBlowerIfTooCold() throws Exception
{
tooCold();
assertEquals("HBchl", hw.getState());
}
Listado 9-5 EnvironmentControllerTest.java (selección más grande)
19. public String getState()
{
String state = "";
state += heater ? "H" : "h";
state += blower ? "B" : "b";
state += cooler ? "C" : "c";
state += hiTempAlarm ? "H" : "h";
state += loTempAlarm ? "L" : "l";
return state;
}
● Sin embargo, se recomienda no utilizar los StringBuffer por ser poco atractivos en las pruebas,
evitarlos a todo lugar en código de producción y en proyectos de bajo presupuesto con pruebas de
tiempo real, por la limitación de recursos y memoria.
Listado 9-6 MockControlHardware.java
20. ● “Todas las funciones de prueba de un test solo deben tener una instrucción
de afirmación para llegar a una conclusión de forma rápida y sencilla.”
● Listado 9-7 SerializedPageResponderTest.java (afirmación única)
public void testGetPageHierarchyAsXml() throws Exception
{
givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued("root", "type:pages");
thenResponseShouldBeXML();
}
Una afirmación por prueba
21. public void testGetPageHierarchyHasRightTags() throws Exception
{
givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued("root", "type:pages");
thenResponseShouldContain( "PageOne", "PageTwo", "ChildOne" );
}
● Funciones que se dividen en tres partes para tener una sola afirmación, para facilitar el
entendimiento, pero se duplica código.
● Para solucionar el problema podríamos crear una nueva clase con una prueba independiente o
nuevas funciones para llamarlas, pero es más complejo que dividir el código y asignar afirmaciones
múltiples, entonces, la regla de asignar una única afirmación se reemplaza por:
● El número de afirmaciones de una prueba debe ser mínimo.
22. ● Probar un solo concepto en cada función de prueba. Evitar funciones de prueba largas que prueben
una miscelánea de cosas tras otra.
● Listado 9-8
/ ** * Pruebas varias para el método addMonths (). * /
public void testAddMonths()
{
SerialDate d1 = SerialDate.createInstance(31, 5, 2004);
SerialDate d2 = SerialDate.addMonths(1, d1);
assertEquals(30, d2.getDayOfMonth());
assertEquals(6, d2.getMonth());
assertEquals(2004, d2.getYYYY());
Un solo concepto por prueba
23. SerialDate d3 = SerialDate.addMonths(2, d1);
assertEquals(31, d3.getDayOfMonth());
assertEquals(7, d3.getMonth());
assertEquals(2004, d3.getYYYY());
SerialDate d4 = SerialDate.addMonths(1, SerialDate.addMonths(1, d1));
assertEquals(30, d4.getDayOfMonth());
assertEquals(7, d4.getMonth());
assertEquals(2004, d4.getYYYY());
}
Las tres funciones de prueba probablemente deberían ser así:
● Dado el último día de un mes con 31 días (como mayo):
1. Cuando agrega un mes, de modo que el último día de ese mes sea el 30
(como junio), entonces la fecha debe ser el 30 de ese mes, no 31.
2. Cuando agrega dos meses a esa fecha, de modo que el último mes tenga
31 días, entonces la fecha debería ser 31.
● Dado el último día de un mes con 30 días (como junio):
1. Cuando agrega un mes, de modo que el último día de ese mes tenga 31 días, entonces la fecha debe ser 30,
no 31.
24. Regla general
Cuando incrementa el mes, la fecha no puede ser mayor que el último día del mes.
Esto implica que incrementar el mes el 28 de febrero debería producir marzo 28.
Falta esa prueba y sería útil escribirla.
Entonces, no son las múltiples afirmaciones en cada sección del Listado 9-8 las que
causan el problema. Más bien, es el hecho de que se está probando más de un
concepto. Entonces probablemente la mejor opción sea en realidad minimizar el
número de afirmaciones por concepto y probar solo una concepto por función de
prueba.
Regla F.I.R.S.T
Rapidez Independecia Repetición
Validación
Automática
Puntualidad
25. ● Las pruebas son tan importantes para la salud de un proyecto como el
código de producción.
● Se debe trabajar en las pruebas hasta que sean concisas y
suficientemente expresivas.
● Es necesario inventar API de prueba que actúen como lenguaje
específico del dominio, para ayuden en la creación de pruebas.
● Mantener una limpieza regular del código de las pruebas para evitar
que se corrompa el código de producción.
Conclusiones
27. Las clases deben ser de
tamaño reducido
Mantener resultados
consistentes
Encapsulación
Organización de clase
Cohesión
El principio de
responsabilidad única
SUBTEMAS
Organizar los cambios
28. Organización de clases
Según la convención estándar de Java, una clase:
• Comienza con una lista de variables.
• Primero, constantes estáticas públicas.
• Segundo, variables estáticas privadas.
• Tercero, variables de instancia privadas.
• Las funciones públicas deben seguir a la lista de variables.
29. Encapsulamiento
• Siuna regla del mismo paquete tiene que invocar una función o acceder a
una variable , hacemos que tenga ámbito protected o de paquete
30. Las clases deben ser de tamaño
reducido
• Con las funciones medimos el tamaño contando líneas físicas. Con las clases
usamos otra medida distinta: las responsabilidades.
32. Pero…
• No solo hay que cuidar que no tenga muchos métodos, sino cuidar
especialmente que la clase no tenga muchas responsabilidades.
• Una clave para identificar las responsabilidades es asociar a las mismas con el
nombre de la clase.
• Cuanto mas ambiguo sea el nombre de una clase más probabilidades hay de
que tenga demasiadas responsabilidades.
• Otra clavees que a la hora de describir la clase no nos encontremos con
palabras como “y”, “o”, “si”, “pero”.
33. Principio de responsabilidad única
• Las clases solo deben tener una responsabilidad ypor ende, solo un motivo
para cambiar.
• Podemos extraer los tres métodos de SuperDashBoard relacionados con la
información de versiones en una clase independiente como Version. La clase
Version es una construcción que se puede reutilizar en otras aplicaciones.
34. Principio de responsabilidad única
• En conclusión: “Los sistemas deben estar formados por
muchas clases reducidas, no por algunas de gran tamaño.
Cada clase reducida encapsula una única responsabilidad,
tiene solo un motivo para cambiar ycolabora con algunas
otras para obtener los comportamientos deseados del
sistema”
35. Cohesión
• Las clases deben tener un número reducido de variables de instancia.
• Los métodos de una clase deben manipular una o varias de dichas variables.
• Cuantas más variables manipule un método, más cohesión tendrá con su
clase.
• Una clase en la que cada variable se usa en cada método tiene una cohesión
máxima.
• La idea es crear una dependencia lógica entre métodos y variables.
36. Cohesión = grado de utilización de las variables de instancia por parte de las
funciones. Queremos clases cohesionadas. Cuando se reduce el tamaño de
las funciones se aumenta el tamaño de variables de instancia (para no
pasarlas como parámetro a las subfunciones) y se pierde cohesión. En ese
caso lo mejor es dividir en subclases.
38. Mantener resultados consistentes en
clases de tamaño reducido
• La división de grandes funciones en otras más pequeña aumenta la
proliferación de clases.
• Sinecesito que dentro de una función 1 se llame a una función 2, en lugar de
mandarle a ésta las variables declaradas en la función 1, estas variables
hacerse de instancia o globales en la clase pero esto implica que se pierda
cohesión yaque acumularían más ymás variables globales que solo existen
para que otras funciones las compartan.
• Cuando esto sucede es conveniente crear otra clase.
39. ● En un sistema limpio, organizamos nuestras clases para reducir el
riesgo de cambio, puesto que en la mayoría de sistemas el cambio es
continuo.
Listado 10-9
Organizándose para el cambio
public class Sql {
public Sql(String table, Column[] columns)
public String create()
public String insert(Object[] fields)
public String selectAll()
public String findByKey(String keyColumn, String keyValue)
public String select(Column column, String pattern)
public String select(Criteria criteria)
public String preparedInsert()
private String columnList(Column[] columns)
private String valuesList(Object[] fields, final Column[] columns)
private String selectWithCriteria(String criteria)
private String placeholderList(Column[] columns)
}
40. ● Lo que podemos identificar en el listado 10-9 es que existe una clase con
varios métodos de los cuales se podrían definir varias responsabilidades,
lo cual en el SRP define que es difícil darles mantenimiento o extenderlo.
● La clase Sql del listado 10-9 podría cambiar cuando agreguemos un
nuevo tipo de declaración, además cambiaria cuando modifiquemos los
detalles de los tipos de declaración por lo que tendríamos 2
responsabilidades , con lo cual viola el SRP.
● SRP dicta que cada clase debe hacer solamente una cosa, ósea tener una
única responsabilidad.
● Si existiese el caso en el se supiera que el software no recibirá una
actualización en el futuro podríamos considerar al código del listado 10-9
como funcional.
Organizándose para el cambio
41. ● Aplicando los principios de responsabilidad única (SRP) al código del
listado 10-9 nos quedaría de la siguiente manera:
Listado 10-10
Organizándose para el cambio
abstract public class Sql {
public Sql(String table, Column[] columns)
abstract public String generate();
}
public class CreateSql extends Sql {
public CreateSql(String table, Column[] columns)
@Override public String generate()
}
public class SelectSql extends Sql {
public SelectSql(String table, Column[] columns)
@Override public String generate()
}
public class InsertSql extends Sql {
public InsertSql(String table, Column[] columns, Object[] fields)
@Override public String generate()
private String valuesList(Object[] fields, final Column[] columns)
}
42. ● Podemos observar que el código se ha vuelto mas simple y entendible,
además el riesgo de que al agregar una nueva funcional rompa otra
importante se va reduciendo.
● Adicional las clases del listado 10-10 admite otro principio clave de
diseño el cual es conocido como OCP (Principio Abierto-Cerrado) el cual
dicta que las clases deben estar abierta para extensiones en el código
pero cerradas para las posibles modificaciones del mismo.
● En el listado 10-10 cumple esto puesto que su código esta abierta para
permitir nuevas funcionalidades sin modificar las funcionalidades ya
existentes.
Organizándose para el cambio
43. ● Dentro de nuestro sistemas existen clases que tienen detalles concretos
los cuales están en riesgo cuando se necesite modificar los mismo. Para
evitar los riesgos ante este tipo de situaciones lo que podemos aplicar
es el principio de inversión de dependencia (DIR).
● El DIR explica que nuestras clases no deben de depender de detalles
concretos sino que deberían de depender de abstracciones como las
interfaces, que lo que hace la interfaz es aislar los detalles concretos
para que el código se libre de depender de estos y que se desacople.
Aislamiento del cambio
44. Ejemplo de Aislamiento del
Cambio
En la imagen podemos observar dos clases; la clase botón y la clase lampara,
podemos observar que la clase botón depende directamente de lampara es
decir que si en la clase lámpara realizamos algún cambio el botón cambiara
también.
Otro aspecto es que el código no es reutilizable puesto que botón esta
simplemente enfocado a lámpara, es decir si en un futuro se quisiera
agregar una nueva clase televisión por ejemplo, la clase botón quedaría sin
utilidad para la clase televisión.
45. Ejemplo de Aislamiento del
Cambio
En la imagen podemos observar que se agrego una interfaz, con ello la
clase botón depende únicamente de abstracciones y esto conlleva a que
pueda ser reusado varias veces.
Adicional si se realiza cambios en la clase lampara la clase botón debería
recibir ningún cambio y la clase lámpara es quien debe adaptarse a la
interfaz definida, además se pueden acoplar varias clases, como
televisor, lavadora, etc.
46. Conclusiones
● Orden dentro de la clase: Contantes estáticas, variables estáticas, variables de
instancia y funciones. De todo ello, primero lo público y después lo privado.
● El tamaño debe ser reducido, debe tener una única responsabilidad, la que
indica su nombre. Nombres a evitar son Manager, Processor, Super ya que
denotan muchas responsabilidades.You can write about the topic here
● Single Responsibility Principle, una clase debe tener un único motivo para
cambiar.
● Open/Closed Principle = las clases deben estar abiertas a extensión y cerradas a
modificación. Los cambios mejor que se hagan extendiendo o introduciendo
nuevas clases, no modificando las existentes.
● La organización ante posibles cambios de nuestra aplicación es muy útil, puesto
que esto lo hace mas legible y fácil de modificar o aumentar ciertas
funcionalidades, de lo contrario se debería realizar nuevamente las clases que no
tengan una organización adecuada.
48. ● Considerar que la construcción es un proceso diferente al uso, el
proceso de inicio siempre es una preocupación que cualquier software
debe abordar. Para ello existe una técnica de diseño llamada la
separación de preocupaciones (SoC).
● SoC nos indica que que evitemos escribir funciones largas y complejas,
por ejemplo si notamos que la función comienza a aumentar de tamaño
posiblemente la función se este ocupando de demasiadas cosas a la
vez. Lo cual lo mas prudente seria refactorizarlo.
Separar la construcción de un sistema de
su uso
49. ● Una forma de separar la construcción del uso, consiste en mover todos
los aspectos de la construcción al main, luego diseñar el resto del
sistema asumiendo que todos los objetos se han construido y
organizado de manera apropiada.
● El flujo de control es fácil de seguir. El main construye los objetos
necesarios para el sistema, luego los pasa a la aplicación, que
simplemente los usa. La aplicación no tiene conocimiento del proceso
del main o de construcción, simplemente espera que todo se haya
construido correctamente.
Separación de main
50. ● Ciertas ocasiones necesitamos que la aplicación sea responsable de la
creación de objetos, por lo que es necesario el uso del patron de diseño
Abstract Factory.
Factories
51. ● Una fabrica abstracta tiene métodos los cuales permite acceder a otras
fábricas independientes y sus familias para obtener la instancia de
algún objeto de dicha familia.
● En la fábrica abstracta se define métodos los cuales nos permitirán
tener una conexión con cada una de las familias independientes para
que sea más sencillo realizar un llamado a la familia que se necesite.
● Una fábrica independiente lo que se realiza es la implementación de la
fábrica abstracta con el objetivo de que podamos elegir a que objeto
deseamos acceder u obtener su instancia, una vez que obtenemos la
instancia de algún objeto también tenemos acceso a los métodos de
dicho objeto.
Factories
52. ● DI nos permite inyectar comportamientos a componentes haciendo que
nuestras piezas de software sean independientes y se comuniquen
únicamente a través de una interface. DI extrae responsabilidades a un
componente para delegarlas en otro, estableciendo un mecanismo a
través del cual el nuevo componente puede ser cambiado en tiempo de
ejecución. Para lograr esta tarea DI se basa en un patrón más genérico
llamado Inversión de Control (Inversion of Control).
● DI tiene como finalidad solucionar un problema común que los
programadores encuentran en la construcción de aplicaciones. Este es,
mantener los componentes o capas de una aplicación lo más
desacopladas posible,
Inyección de dependencia
53. ● Los sistemas de software son únicos en comparación con los sistemas
físicos. Sus arquitecturas pueden crecer incrementalmente, si
mantenemos la adecuada separación de preocupaciones.
● Las arquitecturas EJB no separan las preocupaciones de manera
adecuada, por lo tanto impide que el crecimiento orgánico. Esto vuelve
a EJB en una arquitectura bastante complicada y amplia. Crea
soluciones costosas y complejas.
Ampliar
54. En el desarrollo de cualquier aplicación, existen las llamadas
"preocupaciones transversales", que son actividades comunes de la mayoría
de las aplicaciones: inicio de sesión, seguridad, transacciones
PREOCUPACIONES TRANSVERSALES
55. Proxy: es un patrón de diseño estructural que proporciona un objeto que
actúa como sustituto de un objeto de servicio real utilizado por un cliente.
PROXY DE JAVA
56. AOP: una tecnología para programación orientada a aspectos, que realiza el
mantenimiento unificado de las funciones del programa a través de la
compilación previa y agentes dinámicos durante el tiempo de ejecución.
Spring AOP: El famoso framework para aplicaciones spring en su núcleo
tiene un framework para AOP. Este framework es implementado en java puro
con las anotaciones de java @Aspect o basado en esquema con un XML.
FRAMEWORKS DE JAVA PURO AOP
57. Es la extensión orientada a aspectos de java. Este esta disponible en Eclipse
Fundation. Es el lenguaje de POA más usado debido a que es particularmente
fácil de aprender y utilizar.
AspectJ Aspectos
58. Probar la arquitectura del sistema.
Optimizar la toma de decisiones.
Utilizar los estándares con prudencia cuando añadan un valor
demostrable.
Recomendaciones
59. COOL(COOrdination Language): trata los aspectos de sincronismo entre
hilos concurrentes. (Java)
RIDL (Remote Interaction and Data transfers aspect Language)
MALAJ (Multi Aspect LAnguage for Java)
KALA: Modelos transaccionales avanzados
DIE: Un lenguaje de aspectos de dominio específico para eventos de
IDE
HYPERJ
AspectG(ANTLR)
AspectMatlab
Lenguajes de dominio específico