Hay un concepto importante en las pruebas de software llamado clases de equivalencia . La idea detrás es dividir los datos de entrada en clases para que cada clase de equivalencia le proporcione una nueva prueba unitaria. En la práctica, eso significa que si sospecha fuertemente que dos entradas para la función no proporcionarían información adicional o cobertura de código y son, en cierto sentido, intercambiables, entonces puede usar solo una de ellas.
Usemos un ejemplo con una función que toma dos enteros y los suma. Parece ser bastante simple de probar, y generalmente un desarrollador no pasaría mucho tiempo en eso. Pero intentemos cubrir todas las clases de equivalencia posibles aquí. En aras de la simplicidad, escribiré sobre una función Java con números enteros que sean números entre -2 ^ 31 y 2 ^ 31 – 1.
public int sumOfTwoIntegers (int a, int b) {return a + b;}
- Tengo una aplicación que está a punto de comenzar las pruebas de usuarios externos antes de un lanzamiento público. ¿Cuáles son las mejores soluciones gratuitas o de bajo costo para recopilar comentarios de los usuarios?
- ¿Pueden los ingenieros de software ganar más en trabajo independiente que trabajando en una empresa?
- ¿Cuáles son los mejores métodos para la estimación de proyectos de software?
- ¿Cuál es la relación de mejores prácticas de ingenieros frente a producto en compañías de software?
- ¿Vale la pena comprar un MacBook como programador?
Entonces aquí está nuestra función. Ahora escribamos qué tipos de entradas podemos suministrar aquí. Primero, dividimos nuestras entradas en rangos correctos e incorrectos. Como nuestra función nos proporciona dos enteros como inpust y también devolvemos un entero, tenemos que lidiar con el desbordamiento, en ambos lados del rango de valores enteros. Entonces, las entradas correctas serían dos enteros cuya suma se encuentra dentro del rango de -2 ^ 31 y 2 ^ 31 – 1. Ahora, podemos agregar pruebas que nos mostrarán cómo se comportará nuestra función, cuando estemos fuera de este rango :
sumOfTwoIntegers (Integer.MAX_VALUE, 50); // Vamos a desbordarlo a la derecha
sumOfTwoIntegers (50, Integer.MAX_VALUE); // Voltear los argumentos – caso de prueba separado
sumOfTwoIntegers (Integer.MIN_VALUE, -50); // Ahora a la izquierda
sumOfTwoIntegers (-50, Integer.MIN_VALUE); // Y voltéalos de nuevo
Muy bien, eso parece cubrir los desbordamientos definitivos en ambos lados. Ahora, acerquémonos a los límites de los valores enteros y hagamos una pregunta, ¿qué pasa si nos desbordamos un poco, por uno? ¿Será diferente? Posiblemente. Así que vamos otra vez, pero más cerca de los límites.
sumOfTwoIntegers (Integer.MAX_VALUE, 1); // Vamos a desbordarlo un poco
sumOfTwoIntegers (1, Integer.MAX_VALUE); // Voltea los args nuevamente
sumOfTwoIntegers (Integer.MIN_VALUE, -1); // Ahora a la izquierda
sumOfTwoIntegers (-1, Integer.MIN_VALUE); // Y voltéalos de nuevo
Ok, ahora llegamos a los límites de nuestra entrada. Los límites nos presentan otros cuatro casos de prueba:
sumOfTwoIntegers (Integer.MAX_VALUE – 1, 1); // Ahora llegamos al límite
sumOfTwoIntegers (1, Integer.MAX_VALUE – 1); // Voltea los args nuevamente
sumOfTwoIntegers (Integer.MIN_VALUE + 1, -1); // Ahora a la izquierda
sumOfTwoIntegers (-1, Integer.MIN_VALUE + 1); // Y voltéalos de nuevo
Ok, parece que hemos terminado con los límites por ahora. Pasemos a las entradas comunes que esperaría usar con esta función. No está de más tener dos casos de prueba separados, incluso si todos nuestros valores parecen estar en la misma clase de equivalencia, ya que no hay garantía de que realmente se comporten de la misma manera:
sumOfTwoIntegers (5, 1); // Dos números positivos
sumOfTwoIntegers (1, 5); // Conmutado
sumOfTwoIntegers (78, 23); // Otro par
sumOfTwoIntegers (23, 78); // Conmutado
sumOfTwoIntegers (-5, -1); // Dos números negativos
sumOfTwoIntegers (-1, -5); // Conmutado
sumOfTwoIntegers (-78, -23); // Otro par
sumOfTwoIntegers (-23, -78); // Conmutado
sumOfTwoIntegers (-5, 1); // números positivos y negativos
sumOfTwoIntegers (1, -5); // Conmutado
sumOfTwoIntegers (-78, 23); // Otro par
sumOfTwoIntegers (23, -78); // Conmutado
En realidad, hasta este punto, tratamos con argumentos positivos o negativos, pero también hay un cero que puede estropearlo todo. ¿Qué pasa si tratamos de permanecer en el límite con cero como argumento? ¿Cómo influye en otras entradas?
sumOfTwoIntegers (6, 0); // Positivo y cero
sumOfTwoIntegers (0, 6); // Conmutado
sumOfTwoIntegers (-8, 0); // Negativo y cero
sumOfTwoIntegers (0, -8); // Conmutado
sumOfTwoIntegers (0, 0); // Dos ceros
// Límites con ceros
sumOfTwoIntegers (Integer.MAX_VALUE, 0);
sumOfTwoIntegers (0, Integer.MAX_VALUE);
sumOfTwoIntegers (Integer.MIN_VALUE, 0);
sumOfTwoIntegers (0, Integer.MIN_VALUE);
Bueno, ahora parece que lo cubrimos bastante bien. Entonces, ¿cuántos casos de prueba necesitábamos para cubrir diferentes casos límite para una suma de dos enteros? Un enorme 34 prueba una función con dos entradas de un tipo primitivo que tiene una sola línea de código.
¿Entonces, qué podemos aprender de esto? Primero, nunca puede tener suficientes pruebas para demostrar que su código es “correcto”, ya que no es factible. Simplemente refuta que no funciona para algún subconjunto de las entradas, que deberían representar las clases de equivalencia. En segundo lugar, antes de escribir pruebas unitarias, un desarrollador debe prestar mucha atención a qué tipos de entradas tiene la función y qué combinaciones posibles de ellas proporcionan información adicional sobre cómo las maneja el código. Algunos ejemplos:
- Si es un número, ¿puede causar un desbordamiento? Tal vez la división por cero? ¿Puede ser un número de coma flotante? ¿Puede haber errores debido al redondeo arriba / abajo durante el cálculo?
- Si es una cuerda, ¿cuánto puede durar? Puede estar vacio? ¿Qué pasa si la codificación de la cadena no es estándar?
- Si es un objeto, ¿puede ser nulo? ¿Qué podemos cambiar en el estado del objeto?
Emplee la mentalidad de alguien que quiera romper y / o explotar activamente el sistema, primero imagine que es una caja negra e intente todo tipo de combinaciones de entrada, luego mire el código dentro de la función para obtener una idea de lo que hace con la entrada . Intente causar tanto daño en la etapa de prueba, porque alguien puede intentar causarlo más tarde y el código debe manejarlo. Siempre pruebe su código, es esencial y la falta de pruebas adecuadas puede tener consecuencias desastrosas.
Enlaces:
- Particionamiento de equivalencia – Wikipedia
- Prueba de clase de equivalencia versus prueba de valor límite
- Ingeniería de software de Dewsoft