Capacitación TDD

Actividades

1. Actividad básica para definir el sentido a los test (Junit).

2. Actividad intermedia para aplicar los conceptos de Junit en un proyecto con una estructura parecida a la del proyecto (Junit).

3. Actividad avanzada para utilización de mockito.

1. Al ser la prueba automática, nos dará menos pereza lanzarla con frecuencia. De hecho, lo ideal es hacer un trozo de código y pasar las pruebas. Si hemos fastidiado algo, sabremos que el trozo de código que acabamos de tocar es el que ha introducido el fallo. Si la prueba es larga y manual, seguramente probaremos con menos frecuencia. Si algo se estropea, quizás tengamos que buscar el fallo en el código que hemos hecho durante toda la semana. 

Ventajas

2. Tener las pruebas automáticas nos quita el miedo a tocar el código que ya está funcionando. A veces vemos código que está funcionando pero que no está todo lo bien hecho que debiera. Otras veces, tenemos que arreglar un poco ese código para poder reutilizarlo en una nueva funcionalidad que queremos dar a nuestro programa. Si no tenemos una forma fácil de ver que ese código sigue funcionando después de tocarlo, igual no nos decidimos a tocarlo.

3. Una prueba automática que falla nos da más información que una prueba manual que detecta un fallo. Si una prueba automática falla, nos dice exactamente qué método del código no hace lo que se esperaba de él. Si una prueba manual encuentra un fallo, sólo sabemos que algo en cualquier parte del código no está bien.

Prerrequisitos: 

Tener instalado Junit en un proyecto java.

 

Pasos:

1. Identificar las historias de usuario con el P.O.

2. Determinar los escenarios para cada historia de Usuario.

3. Escribir nuestro test.

4. Crear nuestra clase sin implementar la lógica.

5. Ejecutar los test con Junit (inicialmente falla).

6. Agregamos la lógica a nuestra clase.

7. Ejecutamos test 

8. Nuestro test pasa.

Historia de usuario: 

 

1. Como usuario requiero que al ingresar una palabra pueda verla al revés Por ejemplo (SI - IS).

A partir de la HU de usuario anterior se puede presentar que:

A. El usuario ingrese una palabra de dos letras como 'AB' y quiera observar 'BA'.

 

Actividad

1. Escribamos nuestro test para cubrir el anterior caso.

Solución

public class StringHelperTest {

	@Test
	public void test(){
		StringHelper helper = new StringHelper();
		assertEquals("BA", helper.swapLast2char("AB"));
	}
	
}

2. creamos la clase, con el respectivo método, pero sin implementar aún el la lógica (creemos nuestro coco).

Solución

package co.com.intercredito.sac.service;

public class StringHelper {
	public String swapLast2char(String str)
	{return null;
	}	
}

3. Ahora vamos ejecutar nuestro test con Junit.

4. Ahora vamos a implementar la lógica en nuestra clase para que nuestro desarrollo pase el escenario definido a partir de la HU.

package co.com.intercredito.sac.service;

public class StringHelper {
	public String swapLast2char(String str)
	{
	char firstChar = str.charAt(0);
	char secondChar = str.charAt(1);
	return ""+secondChar+firstChar;
	}	
}

Solución

5. Ahora vamos a correr nuevamente nuestro test exitoso.

B. Ahora se presenta un nuevo caso, al usuario ingresa 'ABDC' el usuario espera visualizar 'ABCD'.

Ahora vamos a agregar un nuevo test para el nuevo escenario.

	@Test
	public void test(){
		StringHelper helper = new StringHelper();
		assertEquals("BA", helper.swapLast2char("AB"));
	}
	
	
	@Test
	public void test2(){
		StringHelper helper = new StringHelper();
		assertEquals("ABDC", helper.swapLast2char("ABCD"));
	}

Ejecutamos los test y observamos que el segundo va a fallar porque aún no hemos implementado el código:

Ahora implementemos la lógica para que nuestro desarrollo pase el test.

Solución

package co.com.intercredito.sac.service;

public class StringHelper {
	public String swapLast2char(String str)
	{
	int length = str.length();	
	String strMinusLast2Chars = str.substring(0,length-2);
	
	char secondLastChar = str.charAt(length-2);
	char lastChar = str.charAt(length-1);
	return strMinusLast2Chars+lastChar+secondLastChar;
	}	
}

Ahora ejecutemos los test para ver si nuestro desarrollo está satisfaciendo los test.

Revisar si hay variables que se repiten en los test, en este caso se debería hacer un refactor sobre los test para colocar variables generales. Lo anterior buscando reducir tiempo de ejecución de los tests.

Solución 

public class StringHelperTest {

	StringHelper helper = new StringHelper();
	
	@Test
	public void test(){
		assertEquals("BA", helper.swapLast2char("AB"));
	}
		
	@Test
	public void test2(){
		assertEquals("ABDC", helper.swapLast2char("ABCD"));
	}
	
}

C. Ahora se presenta un nuevo caso, el usuario ingresa 'ABDCEFGHIJ' y debe visualizar 'ABCDEFGHIJ'

Ahora vamos a agregar un nuevo test para el nuevo escenario.

public class StringHelperTest {

	StringHelper helper = new StringHelper();
	
	@Test
	public void test(){
		assertEquals("BA", helper.swapLast2char("AB"));
	}
		
	@Test
	public void test2(){
		assertEquals("ABDC", helper.swapLast2char("ABCD"));
	}
	
	@Test
	public void test3(){
		assertEquals("ABDCEFGHIJ", helper.swapLast2char("ABCDEFGHIJ"));
	}
	
}

Solución 

Ahora implementemos la lógica para que nuestro desarrollo pase el test.

package co.com.intercredito.sac.rest;

public class StringHelper {
	public String swapLast2char(String str)
	{
	int length = str.length();	
	
	String strMinusLast2Chars = str.substring(0,length-2);
	
	char secondLastChar = str.charAt(length-2);
	char lastChar = str.charAt(length-1);
	return strMinusLast2Chars+lastChar+secondLastChar;
	}	
}
	@Test
	public void test(){
		assertEquals("BA", helper.swapLast2char("AB"));
	}
		
	@Test
	public void test2(){
		assertEquals("ABDC", helper.swapLast2char("ABCD"));
	}
	
	@Test
	public void test3(){
		assertEquals("ABCDEFGHJI", helper.swapLast2char("ABCDEFGHIJ"));
	}
	
	@Test
	public void test4(){
		assertEquals("A", helper.swapLast2char("A"));
	}

D. Nuestro desarrollo también debe cumplir con que al ingresar una letra 'A' el resultado sea la misma letra 'A'.

public class StringHelperTest {

	StringHelper helper = new StringHelper();
	
	@Test
	public void test(){
		assertEquals("BA", helper.swapLast2char("AB"));
	}
		
	@Test
	public void test2(){
		assertEquals("ABDC", helper.swapLast2char("ABCD"));
	}
	
	@Test
	public void test3(){
		assertEquals("ABCDEFGHJI", helper.swapLast2char("ABCDEFGHIJ"));
	}
	
	@Test
	public void test4(){
		assertEquals("A", helper.swapLast2char("A"));
	}
}
package co.com.intercredito.sac.rest;

public class StringHelper {
	public String swapLast2char(String str)
	{
	int length = str.length();	
	
	if (length<2) return str; 
	
	String strMinusLast2Chars = str.substring(0,length-2);
	
	char secondLastChar = str.charAt(length-2);
	char lastChar = str.charAt(length-1);
	return strMinusLast2Chars+lastChar+secondLastChar;
	}	
}

Conclusiones:

* TDD, nos permite pensar y realizar código mantenible.

* Nos permite reducir código (Nos permite abstraer funcionalidades similares).

* La funcionalidad que se realiza para un usuario cumple con los criterios.

* Código mas limpio.

* Desarrollo a la par, nos permite pensar en resultado final a medida que desarrollamos (reemplazamos etapa de diseño).

* Código enfocado en calidad (nuestro código se vuelve documentación).

TDD Intermedio

 

Nuestro desarrollo tendrá: 

 

1. OrderProcessor (Clase de Ordenamiento que tendrá 2 métodos).

2. OrderService (Lógica).

3. OrderDAO (Acceso a datos).

4. DataBase.

 

 

Junit 4  - Annotations

 

@Test: Nos permite marcar las pruebas.

@Before: Es lo primero que se ejecutará antes de cada test (creación de objetos).

@After: Se ejecutará después de cada test.

@BeforeClass: Se ejecutará una sola vez, antes de todos los tests.

@Ignore: Se ignora el test (no es recomendable).

 

Import static org.junit.Assert.*;

 

 

assertTrue(expresión):

indica que el resultado esperado debe ser trueeste no necesita que le pasemos un parámetro con true.

assertTrue(resultado); // correcto
assertTrue(true, resultado); // innecesario.

 

assertFalse(expresión):

Comprueba que expresión evalúe a false.

 

assertEquals(esperado,real):

Comprueba que esperado sea igual a real.

assertNull(objeto):

Comprueba que objeto sea null.

 

assertNotNull(objeto): 

Comprueba que objeto no sea null.

 

assertSame(objeto_esperado,objeto_real):

Comprueba que objeto_esperado y objeto_real sean el mismo objeto.

 

 

assertNotSame(objeto_esperado,objeto_real):

Comprueba que objeto_esperado no sea el mismo objeto que objeto_real.

 

fail(): hace que el test termine con fallo.

El código marcado @Before se ejecuta antes de cada prueba, mientras que @BeforeClass se ejecuta una vez antes de que todo el aparato de prueba. Si la clase de prueba tiene diez pruebas, @Before código se ejecutará diez veces, pero @BeforeClass se ejecutará sólo una vez.

En general, se utiliza @BeforeClass cuando múltiples pruebas necesitan compartir el mismo código de configuración computacionalmente caro. Establecer una conexión de base de datos entra en esta categoría. Puede mover el código de @BeforeClass en @Before, pero tu prueba de funcionamiento puede tomar más tiempo.

//Interface
package com.bharath.trainings.junit;

public interface Greeting {
String greet(String name);
}
package com.bharath.trainings.junit;

public class GreetingImpl implements Greeting {
	@Override
	public String greet(String name){	
		return null;
	}	
}
package com.bharath.trainings.junit;

import static org.junit.Assert.*;

import org.junit.Test;

public class GreetingImplTest {

	@Test
	public void test() {
		fail("Not yet implemented");
	}
}
package com.bharath.trainings.junit;

import static org.junit.Assert.*;

import org.junit.Test;

public class GreetingImplTest {

	@Test
	public void greetShouldReturnAValidOutput() {
		Greeting greeting = new GreetingImpl();
		String result = greeting.greet("Junit");
		assertNotNull(result);
		assertEquals("hello ",result);
	}
}
package com.bharath.trainings.junit;

import static org.junit.Assert.*;

import org.junit.Test;

public class GreetingImplTest {

	@Test
	public void greetShouldReturnAValidOutput() {
		Greeting greeting = new GreetingImpl();
		String result = greeting.greet("Junit");
		assertNotNull(result);
		assertEquals("hello Junit",result);
	}
}

Fallo:

Exitoso:

package com.bharath.trainings.junit;

import static org.junit.Assert.*;

import org.junit.Test;

public class GreetingImplTest {

	@Test
	public void greetShouldReturnAValidOutput() {
		Greeting greeting = new GreetingImpl();
		String result = greeting.greet("Junit");
		assertNotNull(result);
		assertEquals("hello Junit",result);
	}
	
	@Test(expected=IllegalArgumentException.class)
	public void greetShouldThrowAnException_For_NameIsNull(){
		Greeting greeting = new GreetingImpl();
		greeting.greet(null);
	}
}
package com.bharath.trainings.junit;

import static org.junit.Assert.*;

import org.junit.Test;

public class GreetingImplTest {

	@Test
	public void greetShouldReturnAValidOutput() {
		Greeting greeting = new GreetingImpl();
		String result = greeting.greet("Junit");
		assertNotNull(result);
		assertEquals("hello Junit",result);
	}
	
	@Test(expected=IllegalArgumentException.class)
	public void greetShouldThrowAnException_For_NameIsNull(){
		Greeting greeting = new GreetingImpl();
		greeting.greet(null);
	}
	
	@Test(expected=IllegalArgumentException.class)
	public void greetShouldThrowAnException_For_NameIsBlank(){
		Greeting greeting = new GreetingImpl();
		greeting.greet("");
	}
}
package com.bharath.trainings.junit;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

public class GreetingImplTest {

	private Greeting greeting;
	
	@Before
	public void setup(){
	 greeting = new GreetingImpl();	
	}
		
	@Test
	public void greetShouldReturnAValidOutput() {
		String result = greeting.greet("Junit");
		assertNotNull(result);
		assertEquals("hello Junit",result);
	}
	
	@Test(expected=IllegalArgumentException.class)
	public void greetShouldThrowAnException_For_NameIsNull(){
		greeting.greet(null);
	}
	
	@Test(expected=IllegalArgumentException.class)
	public void greetShouldThrowAnException_For_NameIsBlank(){
		greeting.greet("");
	}
}
package com.bharath.trainings.junit;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class GreetingImplTest {

	private Greeting greeting;
	
	@Before
	public void setup(){
	System.out.println("setup");
	 greeting = new GreetingImpl();	
	}
		
	@Test
	public void greetShouldReturnAValidOutput() {
		System.out.println("greetShouldReturnAValidOutput");
		String result = greeting.greet("Junit");
		assertNotNull(result);
		assertEquals("hello Junit",result);
	}
	
	@Test(expected=IllegalArgumentException.class)
	public void greetShouldThrowAnException_For_NameIsNull(){
		System.out.println("greetShouldThrowAnException_For_NameIsNull");
		greeting.greet(null);
	}
	
	@Test(expected=IllegalArgumentException.class)
	public void greetShouldThrowAnException_For_NameIsBlank(){
		System.out.println("greetShouldThrowAnException_For_NameIsBlank");
		greeting.greet("");
	}
	
	@After
	public void teardowm(){
	System.out.println("teardowm");
	greeting = null;	
	}
	
}

THE END

Buenas practicas de desarrollo:

 

1. Single responsability principle

Principio de unica responsabilidad.

"There should never be more than one reason for a class to change." 

 

"No debería haber nunca más de una razón para cambiar una clase."

- Robert C. Martin.

 

 

"Una clase debería concentrarse sólo en hacer una cosa, tener una única responsabilidad, y ésto no significa que sólo tenga un método sino que todos los métodos, públicos o privados, deberían estar enfocados a un solo propósito. Y todo ésto lo que implica es que una clase sólo debería tener una razón para cambiar, tal y como dice la traducción literal del principio".

Capacitacion TDD

By edisonvasquez