Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Las Mejores Practicas para Los Programadores Aplicando Unit Test y Code Coverage
Las Mejores Practicas para Los Programadores Aplicando Unit Test y Code Coverage
Cochabamba – Bolivia
2018
1
Índice de contenido
Resumen ________________________________________________________________________________________________ 4
INTRODUCCIÓN ________________________________________________________________________________________ 5
1.1. Antecedentes _________________________________________________________________________________________ 5
2. TEST DRIVEN DEVELOPMENT __________________________________________________________________ 6
2.1. Ciclo de Test Driven Development _______________________________________________________________ 6
2.1.1. Red __________________________________________________________________________________________________________ 6
2.1.2. Green ________________________________________________________________________________________________________ 7
2.1.3. Refactor _____________________________________________________________________________________________________ 7
6. CONCLUSIONES __________________________________________________________________________________ 39
7. BIBLIOGRAFÍA ___________________________________________________________________________________ 40
Índice de Figuras
3
Resumen
El proceso de desarrollo de software durante los últimos años se ha inclinando por los procesos
de desarrollo ágiles porque estos procesos se adaptan al cambio; el cambio constante de los
requerimientos genera que el código este en constante cambio por lo que también las pruebas
sufren cambios, el test de regresión ejecutado por el equipo de control de calidad de software
puede tener como resultado algunos bugs. El test de regresión es un proceso crítico que
consume bastante tiempo dependiendo de los pasos que tienen que seguir para probar una
funcionalidad, existe mucha probabilidad de que alguna funcionalidad se vea afectada por el
cambio de algún requerimiento.
Los desarrolladores se dieron cuenta de que esto era un gran problema porque si el equipo de
calidad de software no detecta las fallas estos llegarían hasta el cliente. Extreme Programing
una de las metodologías ágiles adoptó el desarrollo de software guiado por pruebas Test Driven
Development (TDD), el cual indica que se tiene que crear una prueba que falle antes de
empezar a programar, esta prueba garantiza que el código funcione correctamente; ante
cualquier cambio de funcionalidad primero se tiene que cambiar o crear una prueba para luego
empezar a codificar el cambio, estas pruebas pueden ejecutarse las veces que uno desee
porque no depende del ejecutor, esto agrega un aspecto de calidad mas al software
desarrollado porque por cada cambio se correrán las pruebas y se detectará la o las
funcionalidades afectadas. Estas pruebas son independientes al test de regresión que el equipo
de calidad de software tiene que realizar porque incluso aplicando TDD puede que aun se
detecten bugs en la funcionalidad pero la posibilidad de entregar funcionalidades libres de bugs
aumenta.
Los cinco principios SOLID un principio por cada letra; Single Responsability Principle (SRP),
Open-Closed Principle (OCP), Liskov Substitution Principle (LSP), The Interface Segregation
Principle (ISP) y el último Dependency-Inversion Principle (DIP). Estos principios hacen que el
código tenga cualidades que facilitan la generación de pruebas unitarias.
Code Coverage es una métrica que ayuda a identificar clases, métodos, bloques y sentencias
de código a las que no se llegaron a cubrir con las pruebas unitarias.
4
INTRODUCCIÓN
El proceso de desarrollo de software ha avanzado bastante al adoptar los procesos agiles como
metodología de desarrollo, acelerando los tiempos de entrega y una adaptación razonable al
cambio. Las metodologías ágiles introdujeron cambios en varias etapas del desarrollo de
software orientados en el diseño, implementación y pruebas. Estas tres etapas mencionadas
están involucradas dentro de Test Driven Development (TDD).
TDD es una práctica de programación que consiste en escribir pruebas antes de empezar a
producir código para que pasen las pruebas (estas dos acciones involucran las etapas de
desarrollo y pruebas). Una vez que el código pase la prueba satisfactoriamente se ingresa en la
etapa de refactorización donde se elimina el código duplicado o se cambia el diseño del código
(esta acción se puede entender como diseño).
1.1. Antecedentes
El desarrollo de software tiene varios problemas sin importar la metodología que se aplique
para el desarrollo de software. El entender correctamente el requerimiento antes de empezar el
desarrollo es importante, las comprensiones parciales del requerimiento genera código con
funcionalidades defectuosas que se desencadenan como bugs en el proceso de testeo, por
tanto se invierte mucho más tiempo y dinero para la corrección de una funcionalidad mal
implementada.
5
2. TEST DRIVEN DEVELOPMENT
El libro de Kent Beck Extreme Programing donde se menciona el Test Driven Development TDD
expresados bajo dos reglas simples:
Nunca escribir una línea de código sin primero escribir una prueba automática que falle.
No escribir pruebas repetidas.
2.1.1. Red
En el primer paso, tenemos que escribir una prueba fallida basada en el requerimiento, esta
prueba fallara porque todavía no se desarrollo el código necesario para cumplir con el
requerimiento; este es el escenario ideal pero no siempre sucede, existen dos razones
principales por las cuales no se presenta este escenario, considerados como retroalimentación
positiva.
6
La primera razón más común sucede cuando se cometió un error al escribir la prueba o que el
código este mal implementado, en este caso se tiene que hacer una doble verificación del
proceso porque la prueba unitaria verifica que el código este correcto y por otro lado el código
verifica que la prueba este bien escrita. [1]
2.1.2. Green
En el segundo paso se comienza a implementar la funcionalidad para la que se escribió la
prueba unitaria, en este paso solo se debe escribir el código suficiente para que la prueba pase.
Esto es difícil de cumplir porque uno por experiencia ya conoce lo que su código necesita para
que este completo. Un ejemplo: se quiere calcular el factorial de un número, siguiendo el
algoritmo de TDD primero escribimos la prueba para el número 0 donde se itene que retornar 1,
el código necesario solo seria retornar 1; continuando con el algoritmo escribimos una prueba
unitaria para el número 1 donde tenemos que retornar 1, Kent Beck llama falsa iteración a este
tipo de práctica, lo recomendable para este caso es escribir el código completo para calcular el
factorial de cualquier número, por tanto escribir pruebas para calcular el factorial parece ser
muy innecesario. Pero se puede escribir pruebas para las excepciones que puedan pasar con
este algoritmo como el factorial de un número negativo o un número faccionario, donde el
factorial no existe. Estas son las pruebas que se podrían escribir. [1]
2.1.3. Refactor
El último paso del ciclo TDD es la refactorización, debido a que en la fase anterior se escribió
código necesario para pasar la prueba puede que se haya creado un algoritmo redundante o
inadecuado; en esta fase se debe limpiar el código creado sin agregar nuevas funcionalidades,
este proceso es una forma de validar el código. Uno siente la necesidad de agregar nueva
funcionalidad en este paso, pero se tiene que evitar porque esta fase es para eliminar el código
duplicado, aumentar la legibilidad y expresividad del código, como también reducir el
acoplamiento y aumentar la cohesión del código. En resumen se debe mejorar el diseño del
código. [1]
Después de terminar de refactorizar el código se debe asegurar de que no se haya afectado la
funcionalidad para la cual se escribió el código, corriendo nuevamente la prueba unitaria para
verificar que la funcionalidad no esté afectada, caso contrario revertir el código modificado en la
7
refactorización y así empezar el ciclo nuevamente porque la prueba unitaria fallo llevándonos al
paso uno conocido como Red.
8
3. PRINCIPIOS S.O.L.I.D.
9
Como se puede ver en esta clase existen dos responsabilidades la primera es la conexión y la
segunda es la comunicación. Los métodos dial y hangup son de conexión; send y recv son
métodos de comunicación datos.
Para cumplir con el principio SRP se tiene que separar las responsabilidades de la siguiente
manera.
Este principio menciona que clases, módulos, funciones, etc deben estar abiertos para
extensiones y cerrados para las modificaciones. Cuando un cambio simple crea un efecto en
cascada o bola de nieve es porque nuestro código no está cumpliendo con este principio. Para
evitar este efecto el principio OCP dice que el comportamiento de una entidad debe poder ser
modificado sin tener que modificar su código fuente. [7]
Estar abiertos para extensión significa que el comportamiento del módulo puede ser extendido,
como los requerimientos de una aplicación pueden cambian nosotros podemos extender el
módulo para soportar nuevos comportamientos.
Estar cerrados a las modificaciones significa que no se tiene que modificar el comportamiento
existente sino extender y crear una nueva implementación que soporte el nuevo requerimiento.
Como se puede observar en el siguiente ejemplo donde no se cumple este principio:
La clase Client usa la clase Server, si nosotros queremos que el cliente use otro servidor
tenemos que modificar la clase Client.
Este principio es el que produce los mayores beneficios para la programación orientada a
objetos como ser flexibilidad, reutilización y mantenibilidad.
11
Figura 5: Diagrama de clases con bajo acoplamiento
Fuente: Elaboración propia
Una forma de que este ejemplo cumpla con el principio OCP es hacer que la clase Server
implemente una Interface donde este el cliente. Se puede decir que es abierto a modificaciones
porque podemos extender la clase Client o Server sin afectar el modelo, es cerrado porque no
se afecta a comportamientos anteriormente definidos.
12
Figura 6: Diagrama de clases que no cumple con LSP
Fuente: Elaboración propia
13
public class Square extends Rectangle {
public Square(Integer with, Integer height) {
super(with, height);
}
La clase Square que extiende en primer lugar no cumple con el principio Open Close Principle
porque tiene un método que no necesita aunque un poco difícil de entender un Cuadrado solo
tiene un solo valor para alto y ancho, la clase Square sobrescribe y trata de solo mantener un
solo valor de sus cuatro lados por su definición.
La clase Client con su método areaVerifier que es el método que no cumple con el principio
Liskov Substitution Principle porque este método recibe como parámetro una clase Rectangle
clase de la que extiende Square según la definición de LSP este método tiene que funcionar de
la misma forma para la clase base como para la extendida, pero no lo hace si se manda un
Square como parámetro este método cambia sus lados haciendo que el ultimo valor en su alto
o ancho sea el valor de sus cuatro lados, en el ejemplo el resultado seria 25. Ahora si
mandamos un rectángulo funciona como es debido dando como resultado 20.
Como se puede observar en el siguiente ejemplo donde se cumple con el principio LSP:
14
Figura 7: Diagrama de clases que cumple con LSP
Fuente: Elaboración propia
public Vehicle() {
this.velocity = 0;
this.isOn = false;
}
La clase Vehicle define los métodos como startEgine() y acceletare() que son implementadas a
su manera por las clases ElectricBus y Car; la clase Driver tiene un método que recibe como
parámetro una clase Vehicle este método ejecuta los métodos startEngine() y accelerate() que
funciona correctamente para cualquier clase que extienda de Vehicle cumpliendo así con este
principio.
Esta solución no cumple con el principio de Interface Segregation Principle (ISP) porque ahora
la clase Door implementa métodos que no usara como el método timeOut. Para cumplir con el
principio (ISP) se puede separar las clases de la siguiente forma.
17
Figura 9: Diagrama de clases que cumple con ISP
Fuente: Elaboración propia
18
3.5. Dependency-Inversion Principle (DIP)
Se puede apreciar que la clase Button depende directamente de la clase Lamp, esta
dependencia implica que cualquier cambio en la clase Lamp afecta a la clase Button; ad emás
19
que el Button no puede ser usado para accionar otro dispositivo. La solución es crear una
abstracción entre las clases Button y Lamp.
20
4. PRUEBAS UNITARIAS
Las pruebas unitarias son un nivel de prueba de software donde se prueban unidades/
componentes individualmente de un software. El propósito es validar que cada unidad del
software funcione según lo diseñado. Una prueba unitaria es la parte comprobable más
pequeña de cualquier software, por lo general tiene pocos parámetros de entrada y casi
siempre uno de salida.
En programación orienta a objetos, la unidad más pequeña es un método, que puede
pertenecer a una clase base o una clase abstracta.
21
4.1.2. Reducir los bugs de regresión
Una de las razones para escribir pruebas unitarias es para reducir los bugs de regresión. Todo
cambio en el código puede afectar funcionalidades de manera no intencional, al trabajar con
metodologías ágiles es inevitable no realizar cambios en las funcionalidades y como
consecuencia cambio de código. Los bugs generados por el cambio pueden ser encontrados
por el equipo de control de calidad, pero si no ocurre esto el bug puede llegar hasta el cliente.
Las pruebas unitarias pueden reducir las posibilidades de que estos bugs ocurran porque el
desarrollador tiene que cambiar las pruebas unitarias por cada cambio de código asegurándose
de resolver los fallos que generaron sus cambios.
Legible, una prueba unitaria debe ser clara y fácil de comprender para que nos ayude a corregir
la funcionalidad si el resultado de la prueba unitaria es fallida.
22
Confiable, las pruebas unitarias solo deben de fallar solo si hay un error de sistema, una prueba
unitaria que falla durante la ejecución de un conjunto de pruebas unitarias o durante las pruebas
de integración hacen pensar que las pruebas unitarias no están bien definidas porque una
prueba unitaria no debería de ser afectado por el tipo de ejecución.
Rápido, los desarrolladores escriben pruebas unitarias para que se puedan ejecutarse
repetidamente para verificar si otras pruebas unitarias fallan y se pueda corregir generando
código confiable y verificado.
Atómico, las pruebas unitarias son independientes no debe confundirse con pruebas de
integración porque esas pruebas si interactúan con otros módulos, las pruebas unitarias no
debería de depender de la red, base de datos, sistema de archivos etc.
Generalmente no se quiere romper la encapsulación de las clases sólo para crear pruebas
unitarias, una de las opciones es crear las pruebas unitarias en el mismo paquete, pero no es
recomendable porque ese código no tiene que ser parte de la puesta en producción. Otra
posibilidad es crear una subcarpeta test para crear dentro las pruebas unitarias para tener los
accesos del paquete, esta solución es más fácil de separar del código que se pondrá en
producción. La última opción es tener paquetes paralelos uno donde este el código fuente y otro
donde estén las pruebas unitarias. Maven genera por defecto los paquetes paralelos. [3]
23
Figura 14: Pruebas dentro de un paquete especifico por ruta. [3]
Fuente: Appel, 2015
@Test
public void addItemOnEmptyList() {
ArrayList<String> names = new ArrayList();
Names.add(“test”);
assertEquals(1, names.size ());
}
@Test
public void addItemOnEmptyList() {
ArrayList<String> names = getArray();
24
Names.add(“test”);
assertEquals(1, names.size());
}
@Before
public void create Array() {
names = new ArrayList<String>();
assertEquals(2147483647, 2147483647);
}
@Test
public void addItemOnEmptyList() {
names.add(“test”);
assertEquals(1, names.size());
}
La segunda sección después de inicializar todos los objetos y precondiciones necesarias para
ejecutar la prueba, este es el punto en el que se ejecuta la funcionalidad al cual se está
aplicando la prueba unitaria esta sección por lo general es solo una llamada a un método.
@Test
public void addItemOnEmptyList() {
names.add(“test”);
}
@Test
public void addItemOnEmptyList () {
assertEquals(1,names.size());
}
La cuarta sección es liberar el entorno dejar en la misma condición antes de que se haya
empezado la ejecución del test, en esta sección se puede usar la anotación @After para limpiar
los objetos creados usados por la prueba unitaria.
@Test
public void addItemOnEmptyList () {
names = null;
}
25
4.5. Buenas prácticas para crear pruebas unitarias
Encontrar el nombre correcto para las clases, métodos y variables para que sean descriptivos,
legibles y entendibles. Usar el prefijo Test antes de que existiera el uso de las anotaciones
@Test eran de mucha ayuda porque ayudaba a diferenciar las clases reales de las clases que
se crean para realizar las pruebas unitarias, usar este prefijo ayudaba a separar las clases si en
el proyecto no usa paquetes paralelos, donde las clases reales están en la misma ruta que las
pruebas unitarias. Si se usa paquetes en paralelo para las pruebas unitarias es opcional el uso
del prefijo Test. Usar el prefijo Test es recomendable porque es de mucha ayuda en proyectos
grandes, con este prefijo es más fácil realizar la búsqueda de archivos.
Como se puede observar el nombre está compuesto por tres partes separadas por una barra
baja aparte de usar notación camel Case entre barras. El propósito de nombrar de esta forma
es hacer legible y de fácil comprensión el propósito de la prueba unitaria.
Otra forma es usar el prefijo should when on todo esto en camel Case
shouldAddUniqueItemWhenAddItemOnItemIsTheSame
@Test
public void nameBigNumberBad() {
assertEquals(2147483647, 2147483647);
}
26
@Test
public void nameBigNumberCorrect() {
final int MAX_VALUE = 2147483647;
assertEquals(2147483647, MAX_VALUE);
}
@Test
public void addNumbersArray() {
Integer[][] arrays = {
{2, 2, 4},
{6, 2, 8},
{19, 2, 21},
{22, 2, 24},
{23, 2, 25}
};
for (int i = 0; i< arrays.length; i++) {
Integer[] numbers = arrays [i];
Integer resultAux = numbers[0] + numbers[1];
Assert.assertEquals(numbers[2], resultAux);
}
}
@RunWith(Parameterized.class)
public class Param {
private int numberOne;
private int numberTwo;
private int result;
@Parameterized.Parameters
public static Collection primeNumbers() {
return Arrays.asList(new Object[][] {
{ 2, 2, 4 },
{ 6, 2, 8 },
{ 19, 2, 21 },
{ 22, 2, 24 },
{ 23, 2, 25 }
27
});
}
@Test
public void testAddArray() {
int resultAux = numberOne + numberTwo;
assertEquals(result, resultAux);
}
}
public AddTest() {
calculator = new Calculator();
}
@Test
public void AddTwoNumbers() {
Integer numberOne = 12;
Integer numberTwo = 1;
Integer result = calculator.add(numberOne, numberTwo);
assertEquals(new Integer(13), result);
}
}
@Test
public void addTwoNumbers(){
Integer numberOne = 12;
Integer numberTwo = 1;
Calculator calculator = createCalculator();
Integer result = calculator.add(numberOne, numberTwo);
assertEquals(new Integer(13), result);
}
}
28
4.5.5. Validar métodos privados a través de métodos públicos
En muchos casos se necesita aplicar una prueba unitaria de un método privado; por lo general
un método privado es un detalle de implementación se puede asumir que existe un método
publico que usa al método privado como parte de su implementación, este método público es al
que se tiene que aplicar la prueba unitaria.
if ("SATURDAY".equals(simpleDateFormat.format(date).toUpperCase())) {
return price / 2;
} else {
return price;
}
}
29
@Test
public void getPriceOnSaturday() {
Promo promo = new Promo();
double price = promo.getDiscountedPrice(2);
assertEquals(1,price);
}
@Test
public void getPriceOnOtherDaysNotSaturday() {
Promo promo = new Promo();
double price = promo.getDiscountedPrice(2);
assertEquals(2,price);
}
El patrón singleton es solo otra forma de manejar variables globales. Los singletons promueven
API oscuras que se basan en dependencias reales e introducen un acoplamiento innecesario
entre componentes, tampoco cumplen con el principio de Single Responsability Principle porque
además de sus tareas principales controlan su propio inicialización y ciclo de vida. Los
singletons pueden hacer que las pruebas unitarias dependan del orden de ejecución ya que
mantiene su estado durante toda su vida útil de la aplicación o el conjunto de pruebas unitarias.
Veamos el siguiente ejemplo:
Usar singletons es una mala práctica que puede y debe evitarse en la mayoría de los casos, se
debe distinguir entre singleton como patrón de diseño y única instancia.
Como única instancia la responsabilidad de crear y mantener una solo instancia es el trabajo de
la aplicación. Normalmente para este caso se usa una fábrica o un contenedor de inyección de
dependencias que crea una sola instancia en algún lugar para que la aplicación pueda usarla y
pasarla a cada objeto que lo necesite, este punto de enfoque de singleton es correcto y
verificable.
El siguiente ejemplo, la clase SessionCache es la clase singleton:
public SessionCache() {
sessions = new HashMap<>();
}
30
public static Map<String, Object> getInstance() {
if (sessions == null) {
sessions = new HashMap<>();
}
return sessions;
}
}
@Test
public void userIsNotOnline() {
boolean isOnlineUser1 = SessionCache.getInstance().containsKey("user1");
assertEquals(false, isOnlineUser1);
}
Una alternativa para no usar clase de tipo singleton es usar la inversión de dependencia como
veremos en el siguiente ejemplo.
@Component
public class SessionCacheComponent {
private Map<String, String> sessions;
public SessionCacheComponent() {
sessions = new HashMap();
}
31
@Test
public void userIsNotOnline() {
SessionCacheComponent sessionCacheComponent = new SessionCacheComponent();
String isOnlineUser1 = sessionCacheComponent.get("user1");
assertEquals(null, isOnlineUser1);
}
@Test
public void shouldReturnSaturday() {
String day = MethodStatics.returnRandomDayOfWeek(daysOfWeek);
assertEquals("Saturday", day);
}
}
Este ejemplo es un metodo no determinístico porque cada que se ejecuta la prueba unitaria el
resultado es diferente por lo tanto nuestro test fallará y no dará el mismo resultado. Lo que hace
que la prueba unitaria no pase todas las veces que se ejecute.
@Test
public void shouldReturnSaturdayWithRandomParameter() {
IRandomGenerator randomGenerator = ()-> 0.8d;
String day = MethodStatics.returnRandomDayOfWeek(daysOfWeek, randomGenerator);
assertEquals("Saturday", day);
}
}
La corrección sobre este código es para que la prueba unitaria sea determinista manteniendo el
método estático fue mandar el número aleatorio como parámetro cosa que podemos controlar
en la prueba unitaria para que sea un solo resultado. Además se está desacoplando la
implementación del generador de números aleatorios, con lo cual para el desarrollador será fácil
cambiarlo por otro tipo de generador de números aleatorios.
33
5. CODE COVERAGE
Code coverage es una medida usada para el testeo de software para determinar cuánto del
código fuente ha sido cubierto por las pruebas unitarias implementadas.
Usando code coverage, el equipo de desarrollo puede ver si su código ha sido probado y si
tiene errores. También ayuda a identificar partes del código que no ha sido probado por tanto se
desconoce si el comportamiento de ese sector de código no abarcado es el correcto.
Los expertos describen code coverage como parte de las pruebas de caja blanca, un método
que examina el código del programa. En algunos casos code coverage se usa para encontrar el
sector de código no cubierto para crear estrategias y pruebas para llegar a cubrir el código y
verificar su correcto funcionamiento.
Resultados altos del code coverage usualmente está asociado con la calidad de código, esta
medida esta expresada entre 0% y 100%; donde cero significa que no se cubrió nada del
código y el 100% que todo el código está cubierto por las pruebas unitarias. Comúnmente el
valor mínimo aceptable para el code coverage es del 80%. Si el código está por debajo del 80%
es un indicador de que se desconoce un 20% de los requerimientos, para incrementar este
indicador es necesario escribir más pruebas unitarias.
Las herramientas de cobertura de código generan reportes de forma diferente dependiendo del
tipo de cobertura. Veremos cuáles son estas medidas y explicar su alcance.
34
5.1.3. Cobertura de sentencia
La cobertura de sentencia rastrea la llamada de declaraciones. Este es un reporte muy
importante que permite localizar de que archivo fue hecho la llamada y que líneas de código no
se han ejecutado desde las pruebas unitarias. [6]
Analizaremos el siguiente código y ver el porcentaje que estamos cubriendo con las pruebas
unitarias.
35
El resultado de le ejecución de la prueba con la cobertura de código
El sector de color verde indica la parte de código que fue cubierto por la prueba unitaria y el
sector marcado de color rojo indica que la parte de código que no fue cubierto por ninguna
prueba unitaria.
Modificando las pruebas unitarias para cubrir la parte de código que no verificado.
36
Figura 18: Resultado de la cobertura de código completo
Fuente: Elaboración propia
37
5.2.3. Las herramientas de Cobertura de Código verifica código existente
Se puede estar perdiendo tiempo tratando de llegar a cubrir el 100% del código dejando de lado
el correcto funcionamiento de las funcionalidades. No se tiene que dejar de lado los nuevos
requerimientos, mientras no se termine de implementar las funcionalidades no se llegara al
100%. Las pruebas unitarias y los reportes de cobertura de código no toman en cuenta el
código que no está escrito, por eso no representa una información del avance del producto de
software. Esto porque podemos tener el 100% de cobertura de código pero el 10% de las
funcionalidades completadas.
38
6. CONCLUSIONES
Aplicar TDD tiene muchas ventajas como se menciono, pero seguir los pasos que TDD implica
no es tarea fácil hay que tener disciplina para empezar a escribir pruebas antes de empezar a
producir codigo; esto suena fácil pero tenemos que aplicarlo para ver que tanto esfuerzo aplica
seguir esas simples reglas.
El diseño adecuado de nuestro código es importante para que nuestro código sea escalable y
robusto, para esto nos ayudamos de los 5 principios SOLID, estos principios simples pero a
veces complicados de cumplir nos aseguran de que nuestro código tenga la capacidad de
adaptarse al cambio como también la facilidad de que se puedan crear pruebas unitarias para
ese código.
Las pruebas unitarias nos ayudan a producir código de mejor calidad, porque garantizamos que
el código generado pasó por varias pruebas que respaldan el correcto funcionamiento del
mismo. Aplicar pruebas unitarias hace que nuestro código también cumpla con diseño porque
nos damos cuenta que nuestro código no tiene un buen diseño si es complicado crear una
prueba unitaria para probar su funcionalidad.
Code coverage es una métrica que los desarrolladores podemos usar para encontrar código al
que no se ha aplicado una prueba, esto nos indica que no estamos escribiendo las suficientes
pruebas unitarias dejando incertidumbre en el correcto funcionamiento del código y tal vez los
sectores no cubiertos por pruebas pueden ser sectores a los que es complicado realizar
pruebas por un mal diseño de software.
39
7. BIBLIOGRAFÍA
[1] Test–Driven Development: 15 years later Documento de referencia (Octubre 2014). PDF
Reference. Recuperado de
https://projekter.aau.dk/projekter/files/204129305/Report_swd903e13_.pdf
[2] Martin C. Robert, Martin Micah. (2007). Agile Principles, Patterns, and Practices in C#.
Primera Edicion. Pearson Education.
[3] Frank Appel. (2015). Testing with JUnit. Master high-quality software development driven
by unit tests. Primera Edicion. Packt Publishing.
[4] Patkos Csaba. (2014). SOLID: Part 3 - Liskov Substitution & Interface Segregation
Principles. Recuperado de https://code.tutsplus.com/tutorials/solid-part-3-liskov-substitution-
interface-segregation-principles--net-36710
[5] Sergey Kolodiy. Unit Tests, How to Write Testable Code and Why it Matters.
https://www.toptal.com/qa/how-to-write-testable-code-and-why-it-matters
[6]Cedric Beust, Hani Suleiman. (). Next Generation Java Testing: TestNG and Advanced
Concepts. (Octubre 15, 2007).Primera Edición. Addison-Wesley Professional.
[7] Carlos B. Jurado. (2010) Diseño Ágil con TDD. Primera Edición. Sefe Creative
40