Archive

Archive for the ‘Testing’ Category

Is your code thread safe?

The last weeks I started to considerate about concurrency testing. I wrote an utility that tries to test if your code is thread safe or not.

When you repeat an experiment many times, the result tends to be the expected

This means that if you throw a die one time, we can not know what result is going to happen, but if we do it thousands of times, we will know that each result is going to happens 1/6 of the times.

In less words, lets repeat this many times at the same time and then check if it is fine or not. The problem here is, how to do it at the same time?.

The utility has 2 classes, ConcurrenceHelper and Task. Task is an interface that you need to implement to provide ConcurrenceHelper the job to do. ConcurrenceHelper will trigger all the tasks “at the same time”.

This is the interface:

public interface Task {

	void execute();
	
}

And this class will trigger all the tasks in different threads. Onces all the threads have executed the tasks, the main thread will leave the method (it means that this method is synchronous).

import java.util.concurrent.CountDownLatch;

public class ConcurrenceHelper {

	public void triggerTasks(final int nThreadsPerTask, final Task ... tasks) throws InterruptedException{
		final CountDownLatch waitForThreadsEnd = new CountDownLatch(nThreadsPerTask*tasks.length);
		final CountDownLatch waitToStart = new CountDownLatch(nThreadsPerTask*tasks.length);
		for(final Task task : tasks){
			for(int i=0;i<nThreadsPerTask;i++){
				final String THREAD_NAME = task+"-"+i;
				new Thread(new Runnable() {
					@Override
					public void run() {
						try {
							waitToStart.countDown();
							// Threads are waiting until countDown=nThreadsPerTask*tasks.length
							waitToStart.await();
							task.execute();
						} catch (InterruptedException e) {
							throw new RuntimeException(THREAD_NAME+" can not wait any more o_O", e);
						}finally{
                        	waitForThreadsEnd.countDown();
                        }
						
					}
				}, THREAD_NAME).start();
			}
		}
		// The main thread is waiting until countDown=nThreadsPerTask*tasks.length
		waitForThreadsEnd.await();
		// At this point all threads have executed the task
	}
	
}

And now lets do some testing:


import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

import java.util.List;
import java.util.Vector;

import org.junit.Test;

public class SamplesTest {

	private final ConcurrenceHelper concurrence = new ConcurrenceHelper();
	private final int CONCURRENCE_TASKS = 10000;
	
	@Test
	public void vectorIsThreadSafe() throws InterruptedException{
		final List<Long> list = new Vector<>();
		concurrence.triggerTasks(CONCURRENCE_TASKS, new Task() {
			@Override
			public void execute() {
				list.add(System.currentTimeMillis());
			}
		});
		System.out.println("Value "+list.size());
		assertEquals(CONCURRENCE_TASKS, list.size());
	}
	
	@Test
	public void notThreadSafeClass() throws InterruptedException{
		final NotThreadSafeClass obj = new NotThreadSafeClass();
		concurrence.triggerTasks(CONCURRENCE_TASKS, new Task() {	
			@Override
			public void execute() {
				obj.i++;
			}
		});
		System.out.println("Value "+obj.i);
		assertNotEquals(CONCURRENCE_TASKS, obj.i);
	}
	
	private class NotThreadSafeClass{
		protected int i;
	}
	
}

We are testing one class that we know that is thread safe (Vector) and other one that is not thread safe (NotThreadSafeClass). It is easier to test that one class is thread safe than checking that is not thread safe. The test vectorIsThreadSafe() will be successfull the 100% of the times, but notThreadSafeClass() will not be successfull the 100%. For that reason, to prevent you to have randomly testing errors, make the testing oriented to check that is thread safe (by the way, is what we really want to test).

To prevent concurrency problems, try to make the classes as much inmutables as possible.

Playing with JerseyTest (Jersey 2.5.1 and DI)

I’m going to try explaining a trivial REST example. The idea is building a basic schema to start playing with Jersey. When I begin to use some framework, I usually develop a test enviroment for failing fast, and that is what I’m going to do.

The next example has these features:

  • Jersey 2.5.1
  • Dependency Injection
  • JUnit for testing

Classes:

  • Resource: it will attend the HTTP calls.
  • Service: it’s an interface with two implementations, Impl1 and Impl2.
  • ServiceProvider: it will give the apropiate implementation of Service per each request call in runtime.
  • TestBinder: it set the bindings into the Resource.

 


import static org.junit.Assert.assertEquals;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;

import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

public class JerseyInjectionTest extends JerseyTest {

	private static final String EXPECTED_CONTENT = "any string :P";

	/**
	 * Checks that the Resource uses Impl1.class
	 */
	@Test
	public void invokeImpl1(){
		invoke(Impl1.class);
	}
	
	/**
	 * Checks that the Resource uses Impl2.class
	 */
	@Test
	public void invokeImpl2(){
		invoke(Impl2.class);
	}
	
	/**
	 * Checks that Resource.anyContent has always the value of EXPECTED_CONTENT
	 */
	@Test
	public void checkContent(){
		Response response = target("example/content").request().get();
		assertEquals(EXPECTED_CONTENT, response.readEntity(String.class));
	}
	
	private <T extends Service> void invoke(Class<T> service){
		final String serviceName = service.getName();
		Response response = target("example/"+serviceName).request().get();
		assertEquals(service.getName(), response.readEntity(String.class));
	}
	
	/**
	 * Register the Resource and TestBinder in the Application
	 */
	@Override
	protected Application configure() {
		return new ResourceConfig() {
			{
				register(new TestBinder());
				register(Resource.class);
			}
		};
	}

	@Path("/example")
	public static class Resource {

		@Inject
		Service service;
		@Inject
		String anyContent;

		/**
		 * Returns the name of the Service's implementation
		 */
		@GET
		@Path("/{serviceClass}")
		public Response getDynamicInvokedService() {
			return Response.ok(service.getClass().getName()).build();
		}

		/**
		 * Returns always the value of anyContent
		 */
		@GET
		@Path("/content")
		public Response getStaticContent() {
			return Response.ok(anyContent).build();
		}

	}
	
	/**
	 * This class will help Resource to set the @Inject fields.
	 */
	public static class TestBinder extends AbstractBinder{

		@Override
		protected void configure() {
			bindFactory(ServiceProvider.class).to(Service.class);
			bind(EXPECTED_CONTENT).to(String.class);
		}
		
	}

	/**
	 * This class will instance a Services's implementation
	 * per each time that the Resource is called.
	 */
	@RequestScoped
	public static class ServiceProvider implements Factory<Service> {

		private final String serviceName;

		public ServiceProvider(@PathParam("serviceClass") String serviceName) {
			this.serviceName = serviceName;
		}

		@Override
		public void dispose(Service arg0) {}

		@Override
		public Service provide() {
			try {
				return (Service) Class.forName(serviceName).newInstance();
			} catch (Exception e) {
				return null;
			}
		}

	}

	/**
	 * Dummy services
	 */
	public static interface Service {}
	public static class Impl1 implements Service {}
	public static class Impl2 implements Service {}

}

Now we can try new features easily.

I hope that helps.

Programación orientada a aspectos con Guice

Me gusta la programación orientada a aspectos por la cantidad de ifs que te quita. Ademas son ifs que tienes que ir repitiendo a lo largo del código.
Se me ha ocurrido hacer un pequeño ejemplo con JUnit. Vamos a hacer las tipicas comprobaciones de:

Las vamos a hacer de las dos formas, con aspectos y sin aspectos, demostrando que el resultado es el mismo, pero con aspectos queda mas limpio.

Antes de empezar con el Test, hay que configurar Shiro. Explicandolo por encima, tenemos un fichero que se llama shiro.ini, donde he definido los usuarios con su rol.

[users]
root = secret, admin
guest = guest, guest

[roles]
admin = *

También hay que configurar el modulo de Shiro para Guice, donde se le dice que se configure en base al contenido de shiro.ini:

package com.tododev.shiro;

import org.apache.shiro.config.Ini;
import org.apache.shiro.guice.ShiroModule;
import org.apache.shiro.realm.text.IniRealm;

import com.google.inject.Provides;

public class MyShiroModule extends ShiroModule{

	protected void configureShiro() {
        try {
            bindRealm().toConstructor(IniRealm.class.getConstructor(Ini.class));
        } catch (NoSuchMethodException e) {
            addError(e);
        }
    }

    @Provides
    Ini loadShiroIni() {
        return Ini.fromResourcePath("classpath:shiro.ini");
    }

}

Ahora vamos ya con el test. Este test contiene 4 metodos de test, que comprueban lo siguiente sobre el método de la interfaz Interface.doSomething:

  • Si no estas autenticado lanza una UnauthenticatedException.
  • Si no tienes el rol de “admin” lanza una UnauthorizedException.
  • Si le pasas el parámetro a nulo lanza una ConstraintViolationException.
  • Si estas autenticado, con el rol de “admin” y no pasas el parámetro a nulo, no lanza excepciones.

La gracia es que el test lo vamos a hacer con dos implementaciones diferentes de Interface.class:

  • Aop.class
  • NoAop.class

Es decir, se van a hacer 2 x 4 = 8 tests. El resultado tiene que se el mismo.


package com.tododev.shiro;

import static org.junit.Assert.fail;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.constraints.NotNull;

import org.apache.bval.guice.Validate;
import org.apache.bval.guice.ValidationModule;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.guice.aop.ShiroAopModule;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

/**
 * Vamos a hacer que el test se ejecute con la clase Parameterized. Esta clase
 * nos ejecutara 2 veces todos los tests, insertando en el constructor los
 * valores del metodo data().
 * 
 * @author jorge
 * 
 */
@RunWith(Parameterized.class)
public class GuiceAopTest {

	private final static String ADMIN_ROLE = "admin";
	private final UsernamePasswordToken ADMIN = new UsernamePasswordToken(
			"root", "secret");
	private final UsernamePasswordToken GUEST = new UsernamePasswordToken(
			"guest", "guest");
	private final Injector injector;

	/**
	 * Cuando se crea la instancia de GuiceAopTest.class, se le inserta uno de
	 * los valores del metodo data().
	 * 
	 * @param injector
	 */
	public GuiceAopTest(Injector injector) {
		this.injector = injector;
	}

	/**
	 * Devuelve una coleccion que contiene un inyector con AOP y otro que no. La
	 * idea es demostrar que los tests pasan satisfactoriamente de las dos
	 * formas posibles.
	 * 
	 * @return
	 */
	@Parameters
	public static Collection<Object[]> data() {
		Injector withAop = Guice.createInjector(new ShiroAopModule(),
				new MyShiroModule(), new ValidationModule(),
				new AbstractModule() {
					@Override
					protected void configure() {
						bind(Interface.class).to(Aop.class);
					}
				});
		Injector noAop = Guice.createInjector(new MyShiroModule(),
				new AbstractModule() {
					@Override
					protected void configure() {
						bind(Interface.class).to(NoAop.class);
					}
				});

		Object[][] data = { { noAop }, { withAop } };
		return Arrays.asList(data);
	}

	public static interface Interface {
		void doSomething(String value);
	}

	/**
	 * Implementacion preparada para tener aspectos.
	 * 
	 * @author jorge
	 * 
	 */
	public static class Aop implements Interface {

		@Validate
		@RequiresRoles(ADMIN_ROLE)
		@Override
		public void doSomething(@NotNull String value) {
			System.out.println(getClass() + ": " + value);
		}

	}

	/**
	 * Hace lo mismo que hace Aop, pero esta no usara aspectos.
	 * 
	 * @author jorge
	 * 
	 */
	public static class NoAop implements Interface {

		@Override
		public void doSomething(String value) {
			if (value != null) {
				Subject currentUser = SecurityUtils.getSubject();
				if (currentUser.isAuthenticated()) {
					if (currentUser.isPermitted(ADMIN_ROLE)) {
						System.out.println(getClass() + ": " + value);
					} else {
						throw new UnauthorizedException();
					}
				} else {
					throw new UnauthenticatedException();
				}
			} else {
				throw new ConstraintViolationException(
						new HashSet<ConstraintViolation<?>>());
			}
		}

	}

	/**
	 * Preparamos Shiro para ser usado.
	 */
	@Before
	public void before() {
		SecurityManager securityManager = injector
				.getInstance(SecurityManager.class);
		SecurityUtils.setSecurityManager(securityManager);
	}

	/**
	 * Deslogueamos al usuario si estuviera logueado.
	 */
	@After
	public void after() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.logout();
	}

	/**
	 * Vefirica que si no estas logueado salta una UnauthenticatedException
	 */
	@Test(expected = UnauthenticatedException.class)
	public void unauthenticated() {
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething("any value");
		fail("Unreachable code");
	}

	/**
	 * Verifica que si no tienes los permisos adecuados salta una
	 * UnauthorizedException
	 */
	@Test(expected = UnauthorizedException.class)
	public void unauthorized() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.login(GUEST);
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething("any value");
		fail("Unreachable code");
	}

	/**
	 * Verifica que si llamas al metodo con el String nulo, salta una
	 * ConstraintViolationException
	 */
	@Test(expected = ConstraintViolationException.class)
	public void constraintViolation() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.login(ADMIN);
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething(null);
		fail("Unreachable code");
	}

	/**
	 * Verifica el caso de que todo es correcto y no hay excepciones
	 */
	@Test
	public void allowed() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.login(ADMIN);
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething("any value");
	}

}


El resultado de los test es este:


Los 8 tests han pasado bien.

Este es el resultado que se muestra en la consola:

class com.tododev.shiro.GuiceAopTest$NoAop: any value
class com.tododev.shiro.GuiceAopTest$Aop$$EnhancerByGuice$$4ee6b586: any value

Os podeis fijar que la clase Aop “ha sido mejorada” por Guice, añadiendole los aspectos de Shiro y BVal. Lo curioso es que en las clases compiladas vemos lo siguiente:


La clase que aparece señalada debería llamarse GuiceAopTest$Aop$$EnhancerByGuice$$4ee6b586, pero se llama GuiceAopTest$Aop. Esto es porque Guice no hace los aspectos en tiempo de compilación, sino en ejecución. Esto tiene algunas limitaciones, pero en muchos casos resulta ser suficiente.

En conclusión:

Se puede comprobar lo limpio que queda el código usando aspectos. Así es mas fácil ver qué es lo que hace el método doSomething(). Además, esas anotaciones las podemos aprovechar y llevar a otros métodos que requieran esas comprobaciones.

Problema con Singletons y patron estrategia

El patron estrategia es uno de los que mas “ifs” te pueden quitar en el codigo. Un ejemplo sencillo de como funciona seria imaginar una partida de ajedrez. Sin el patron estrategia, cada vez que un jugador moviese una ficha, el programa tendria que comprobar de que ficha se trata para calcular sus posibles movimientos. Usando el patron estrategia, es la propia ficha la que sabe como moverse y asi nos ahorramos hacer las comprobaciones (ifs).

El problema es que hay veces que las diferentes estrategias cuestan mucho instanciarlas. Por ejemplo porque hace alguna llamada HTTP, o tiene que ir a base de datos a recuperar informacion al levantarse, etc. Este tipo de clases tan costosas suele ser buena idea hacerlas Singleton. El problema de los singleton, es que tienen que ser inmutables (no deberiamos modificar su contenido) o podriamos tener problemas de concurrencia. En nuestro ejemplo del ajedrez, esto supondria que tendriamos que pasar por parametros de metodo las coordenadas a donde queremos mover, en vez de por constructor. Esto seria la solucion definitiva en una partida de ajedrez, ya que solo se necesitan 2 coordenadas para posicionar una figura en el espacio, pero si se tratase del parchis necesitariamos solo 1, ya que es unidireccional. Este detalle no parece importante, pero imagina que estas en un proyecto bastante grande y tienes que estar ignorando parametros del metodo en funcion de si se trata de ajedrez o parchis.

Estaba interesado en dar una solucion flexible al problema del patron estrategia con singletons. La idea del siguiente ejemplo, es hacer que las estrategias sean lo mas limpias posibles, sin exceso de parametros y que sean singletons.

El ejemplo consiste en una interfaz, con dos implementaciones. Cada implementacion es singleton y maneja un objeto de datos diferente (DTO). He usado Guice para ello:

package com.tododev.guice;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.logging.Logger;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;

import org.junit.Test;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.Provides;

public class StrategySingletonTest {

	private static final Logger log = Logger.getLogger(StrategySingletonTest.class.getName());
	private final Injector injector = Guice.createInjector(new MyModule());
	private final static String PARAM_1 = "paramDto1";
	private final static String PARAM_2 = "paramDto2";
	
	@Test
	public void test(){
		Logic<?> logicDto1 = injector.getInstance(LogicDto1.class);
		logicDto1.watchDto();
		
		// Comprobamos que aunque son singletons, los DTO's que instancian son diferentes
		assertNotSame(logicDto1.getInstance(), logicDto1.getInstance());
		
		Logic<?> logicDto1SameInstance = injector.getInstance(LogicDto1.class);
		
		// Comprobamos que son la misma instancia
		assertEquals(logicDto1, logicDto1SameInstance);
		
		
		Logic<?> logicDto2 = injector.getInstance(LogicDto2.class);
		logicDto2.watchDto();
		
	}
	
	
	// Dto que manejara la implementacion LogicDto1
	private static class Dto1{
		private final String field;
		public Dto1(String field) {
			this.field = field;
		}
	}
	
	// Dto que manejara la implementacion LogicDto2
	private static class Dto2{
		private final int integer;
		public Dto2(int integer) {
			this.integer = integer;
		}
	}
	
	// Definimos la forma de instanciarse Dto1
	private static class Dto1Provider implements Provider<Dto1>{

		private final HttpServletRequest request;
		
		@Inject
		public Dto1Provider(HttpServletRequest request){
			this.request=request;
		}
		
		@Override
		public Dto1 get() {
			return new Dto1(request.getParameter(PARAM_1));
		}
		
	}
	
	// Definimos la forma de instanciarse Dto2
	private static class Dto2Provider implements Provider<Dto2>{

		private final HttpServletRequest request;
		
		@Inject
		public Dto2Provider(HttpServletRequest request){
			this.request=request;
		}
		
		@Override
		public Dto2 get() {
			return new Dto2(Integer.parseInt(request.getParameter(PARAM_2)));
		}
		
	}
	
//	Decimos que los DTOs se consiguen a traves de los providers y
//	 introducimos las implementaciones de las interfaces
//	 para que guice sepa que existen
	private static class MyModule extends AbstractModule{

		@Override
		protected void configure() {
			bind(Dto1.class).toProvider(Dto1Provider.class);
			bind(Dto2.class).toProvider(Dto2Provider.class);
			bind(LogicDto1.class);
			bind(LogicDto2.class);
		}
		
//		Devuelve un mock de HttpServletRequest, que es con lo que
//		 formaremos los DTOs
		@Provides
		public HttpServletRequest getRequest(){
			HttpServletRequest request = mock(HttpServletRequest.class);
			when(request.getParameter(PARAM_1)).thenReturn("Jorge");
			when(request.getParameter(PARAM_2)).thenReturn("28");
			return request;
		}
		
	}
	
	private static interface Logic<DTO> {
		void watchDto();
		DTO getInstance();
	}
	
	@Singleton
	private static class LogicDto1 implements Logic<Dto1>{

		@Inject
		private Provider<Dto1> provider;
		
		@Override
		public void watchDto() {
			Dto1 dto = getInstance();
			log.info("I'm "+dto+" and my name is: "+dto.field);
		}

		@Override
		public Dto1 getInstance() {
			return provider.get();
		}
		
	}
	
	@Singleton
	private static class LogicDto2 implements Logic<Dto2>{

		@Inject
		private Provider<Dto2> provider;
		
		@Override
		public void watchDto() {
			Dto2 dto = getInstance();
			log.info("I'm "+dto+" and I am: "+dto.integer);
		}

		@Override
		public Dto2 getInstance() {
			return provider.get();
		}
		
	}
	
}

Esta es la salida que obtendremos:
I’m com.tododev.guice.StrategySingletonTest$Dto1@6ae441ee and my name is: Jorge
I’m com.tododev.guice.StrategySingletonTest$Dto2@5ef4f91d and I am: 28

Este es el grafo de las dependencias que tiene el inyector:

example

Crea tus propios Matchers

Si no sabes lo que es un Matcher, pasa por la siguiente direccion antes de leer mi articulo:

http://mockito.googlecode.com/svn/branches/1.5/javadoc/org/mockito/Matchers.html

A menudo testeando necesitamos hacer algún tipo de checkeo que Mockito no ha implementado por nosotros, pero afortunadamente nos ofrecen una forma de hacerlo. A groso modo, las implementaciones de matchers de mockito consisten en comprobar que “el metodo se ha invocado con cualquier parametro” o que “se ha invocado con exactamente unos parametros definidos por ti”. Pero a veces no nos interesa ni tanto, ni tan poco.

Por ejemplo, uno de los problemas que podemos encontrar en mysql es que en las fechas no se guardan los milisegundos. Como consecuencia, la fecha que vamos a introducir es diferente que la que recogeremos una vez persistida, pues habremos insertado una fecha que en milisegundos es 123456789234 y se recogera 123456789000. Asi que cuando queramos matchear que la fecha que hemos introducido es la misma que la persistida, tendremos un fallo. A lo mejor, queremos hacer que no se matchen los milisegundos. Vamos a ello:

Debemos heredar de la clase ArgumentMatcher, que nos obligara a implementar la funcion matches. Este metodo es el que ejecuta el mockito cuando matchea. Como argumento, tiene un Object que es el objeto que realmente se ha invocado:


public class DateMatcher extends ArgumentMatcher<Date>{

private Date dateBeforePersist;

public DateMatcher(Date dateBeforePersist){
this.dateBeforePersist=dateBeforePersist;
}

@Override
public boolean matches(Object argument) {
Date persistedDate=(Date)argument;
//Comparamos en segundos en vez de milisegundos
return persistedDate.getTime()/1000==dateBeforePersist.getTime()/1000;
}

}

Una vez hecho esto, donde antes teniamos:

Date fecha=new Date();
//Comprueba que se ha invocado someMethod con exactamente esa fecha (milisegundos incluidos)
verify(mock).someMethod(eq(fecha));

Pondremos:

Date fecha=new Date();
//Comprueba que se ha invocado someMethod con esa fecha en segundos
verify(mock).someMethod(argThat(new DateMatcher(fecha)));

Categories: Desarrollo, Testing Tags: ,

Automatización de Tests: QTP (Quick Test Professional) Vs Selenium

Interesante comparativa entre herramientas de automatización. Personalmente, no he probado QTP, y no he podido enredar con Selenium todo lo que me hubiera gustado, pero la siguiente comparativa inclina claramente la balanza. Sobre todo en los casos en que se está introduciendo la automatización de tests. En los demás casos, el hecho de estar ya trabajando con una herramienta concreta aporta un peso que podría nivelar o incluso volcar la balanza hacia el otro lado.

Quick Test Professional Vs Selenium | Execute Automation.

Categories: Desarrollo, Testing Tags: , , ,

Errores comunes al hacer pruebas de software de nuestro código

Los desarrolladores cada vez estamos más concienciados de la importancia de las pruebas de software. Pero no podemos pasar por alto la calidad de estas pruebas, ya que podemos caer en errores no contemplados sin darnos cuenta. No es suficiente con hacer un par de tests y pensar que todo está todo correcto porque nuestra aplicación pasa esos tests, es importante pararse a pensar si cómo hemos planteado las pruebas es la forma correcta para detectar errores reales del código.

A continuación se mostrarán algunos errores comunes a la hora de hacer tests extraídos de un interesante post en Dzone sobre testing.

Test unitarios

Es sencillo probar una clase de utilidad con sólo llamar a todos los métodos de la clases, pasando valores y comprobando si el resultado es el esperado. Aquí caemos en el error de hacer pruebas del estilo: 1+1=2 y 2+1=3. Pero no hacemos, por ejemplo, pruebas de casos limites como cuando los valores son null o si se provoca una excepción, estas quedan muchas veces fuera de nuestras pruebas.

El uso de Mocks

Si nos encontramos en el caso de que queremos probar la capa de servicio, el DAO lo solemos simular manualmente. Un terrible error que puede llevar a confusiones en los test, para hacer este trabajo deberíamos usar un Mock que nos permite hacer pruebas más cercanas a la realidad para eso están creados.

Podemos elegir entre varios framework como Mockito o EasyMock, entre los más conocidos. Pero tener cuidado de elegir el correcto, el exceso de simplicidad o, por el contrario, el de complejidad nos puede traer algún que otro quebradero de cabeza.

Test de integración

En los test de integración cubrimos las diferentes piezas que trabajan en conjunción de nuestra aplicación. Una de ellas es la capa DAO, con la que intentamos validar el funcionamiento conjunto con los parámetros de entrada, las conexiones de base de datos y si sus respectivas queries son las correctas.

Solemos caer en el error de no usar datos que se correspondan con los reales, por lo que nuestros test pueden ser trucados con parámetros de entrada que reciban de la base datos información errónea o incompleta haciendo malabarismos manualmente para comprobar que el test es correcto. También es importante poder cargar datos de tal forma que podamos volver de nuevo al estado inicial.

Test funcionales

Después de probar buena parte de nuestro código técnicamente, no debemos olvidar los bugs funcionales. A través de los test funcionales podemos detectar esos errores. Aunque tengamos la percepción de que son difíciles y costosos de mantener más lo es hacer eso manualmente.

Selenium es una gran framework para grabar este tipo de test con el navegador, aunque caemos en el error de sólo hacerlos ahí. Selenium trackea los componentes HTML, por lo que si cambiamos algo de la página romperemos el test inmediatamente y no necesariamente es porque esté mal. Una opción más robusta es usar API WebDriver que proporcionar una interfaz de programación alternativa para testear con una API orientada a objetos.

Otra cosa que tenemos que tener en cuenta es dejar también en los test cada elemento o dato en su estado inicial. Si en un test hacemos login, debemos recordar cerrar sesión para no interferir en posteriores test o al contrario.

Artículo sacado de GenbetaDev

Categories: Desarrollo, Testing Tags: ,
%d bloggers like this: