Una técnica de prueba utilizada habitualmente es analizar los diferentes estados abstractos que puede tener una clase. El estado de un objeto se suele definir como una restricción en los valores de sus atributos. Según el estado del objeto, las llamadas a algunos métodos pueden ser válidas o no, o es posible que cambie el comportamiento del método.
En general, el proceso de utilizar técnicas de prueba basadas en estado es el siguiente:
Para ver cómo funciona este proceso, tengamos en cuenta la clase MoneyBag de JUnit.
class MoneyBag implements IMoney { private Vector fMonies= new Vector(5); public IMoney add(IMoney m) { return m.addMoneyBag(this); } public IMoney addMoney(Money m) { return MoneyBag.create(m, this); } public IMoney addMoneyBag(MoneyBag s) { return MoneyBag.create(s, this); } void appendMoney(Money aMoney) { if (aMoney.isZero()) return; IMoney old= findMoney(aMoney.currency()); if (old == null) { fMonies.addElement(aMoney); return; } fMonies.removeElement(old); IMoney sum= old.add(aMoney); if (sum.isZero()) return; fMonies.addElement(sum); } private Money findMoney(String currency) { for (Enumeration e=fMonies.elements();e.hasMoreElements();) { Money m= (Money) e.nextElement(); if (m.currency().equals(currency)) return m; } return null; } private boolean contains(Money m) { Money found= findMoney(m.currency()); if (found == null) return false; return found.amount() == m.amount(); } }
El primer paso en la utilización de técnicas de prueba basada en estado es definir los estados. En la segunda línea del código, se puede ver que la clase MoneyBag puede tener de 0 a 5 objetos Money.
private Vector fMonies= new Vector(5);
A partir de este análisis, puede crear un modelo de estado con los estados siguientes:
En este ejemplo, puede definir las restricciones siguientes en el atributo fMonies, como se muestra en la tabla siguiente:
Estado | Restricción |
---|---|
EmptyBag | fMonies.size()==0 |
PartiallyFullBag | (fMonies.size()>0) && (fMonies.size()<5) |
FullBag | fMonies.size()==5 |
Aunque no siempre es necesario definir estos estados formalmente, puede ser útil al definir datos de prueba o si desea comprobar el estado del objeto durante un caso concreto.
El paso siguiente es definir las transiciones posibles entre estados y determinar qué desencadena una transición de un estado a otro. En general, cuando se prueba una clase, se desencadena una transición al invocar un método. Por ejemplo, la transición del estado EmptyBag al estado PartiallyFullBag se desencadena mediante una llamada a appendMoney.
Por tanto, algunas transiciones posibles podrían definirse del modo siguiente:
En resumen, para cada estado identificado, se deben listar:
Las pruebas suelen consistir en casos prácticos en los que se prueba el objeto siguiendo un itinerario determinado a través de la máquina de estado. Dado que el número de itinerarios posibles en la máquina de estado es normalmente infinito, no es práctico probar cada posible itinerario. En su lugar, asegúrese de realizar las tareas siguientes:
Siempre que sea posible, compruebe el estado del objeto que se prueba en todo el caso práctico para garantizar que el modelo de estado teórico definido sea realmente el implementado por la clase que se prueba. Tras finalizar estas transiciones, puede probar su robustez llamando a métodos de forma aleatoria y comprobando que nunca se violen las constantes de las clases. Por ejemplo, la clase MoneyBag siempre debe ser un conjunto de objetos Money que nunca sean de la misma moneda.
Puede utilizar el patrón de prueba basado en caso que acompaña al producto para crear casos de prueba.
Finalmente, debe elegir valores de prueba para cada estado individual. Elija valores de prueba exclusivos y no vuelva a utilizar valores que ya haya empleado previamente en otras pruebas. Esta estrategia proporciona una mayor diversidad en la suite de pruebas e incrementa las posibilidades de detectar errores. Para obtener información detallada sobre la definición de valores de prueba adecuados, consulte Técnicas de particionamiento de datos.