¿Cómo se escriben los casos de prueba unitaria?

14

A veces termino escribiendo casos de prueba unitarios para el código que otros desarrolladores han escrito. Hay ocasiones en que realmente no sé qué intenta hacer el desarrollador (la parte comercial) y simplemente manipulo el caso de prueba para obtener la línea verde. ¿Son normales estas cosas en la industria?

¿Cuál es la tendencia normal? ¿Se supone que los desarrolladores deben escribir casos de prueba unitaria para el código que escribieron ellos mismos?

Vinoth Kumar CM
fuente
2
"fuerza de"? ¿Qué significa "dint"?
S.Lott

Respuestas:

12

Intente leer esta publicación de blog: Escribir excelentes pruebas unitarias: mejores y peores prácticas .

Pero hay innumerables otros en la web.

En respuesta directa a sus preguntas ...

  1. "Tendencia normal": supongo que esto podría diferir de un lugar a otro, lo que es normal para mí podría ser extraño para los demás.
  2. Yo diría (en mi opción) que el desarrollador que escribe el código debe escribir la prueba, idealmente utilizando métodos como TDD, donde escribiría la prueba antes del código. ¡Pero otros pueden tener diferentes métodos e ideas aquí!

¡Y la forma en que describió la redacción de las pruebas (en su pregunta) es totalmente incorrecta!


fuente
9

Este enfoque hace que la prueba unitaria no tenga valor.

Debe hacer que la prueba de la unidad falle cuando alguna acción real no funciona según lo previsto. Si no lo hace así, y tal vez incluso escriba la prueba antes del código para probar, es como tener alarmas de humo que no funcionan.


fuente
8
Esto no es completamente cierto. O más bien, es cierto en un mundo ideal, pero desgraciadamente, a menudo estamos lejos de eso. Considere tener código heredado sin pruebas y sin especificaciones, y sin nadie que pueda contarle de manera confiable los detalles actualizados, qué se supone que debe hacer un código específico (esto es realidad en una gran proporción de proyectos existentes). Incluso en este caso, puede valer la pena escribir pruebas unitarias para bloquear el estado actual del código y asegurarse de no romper nada con futuras refactorizaciones, correcciones de errores o extensiones.
Péter Török
2
Además, supongo que querías decir "escribe la prueba después del código para probar", ¿verdad?
Péter Török
@ Péter, la redacción salió mal: lo entendiste bien. Pero, si decides escribir pruebas, deberían hacer algo para ser útil. Invocar ciegamente el código que dice que es una prueba, en mi opinión, no es una prueba.
ørn, si quiere decir que debemos tener afirmaciones significativas en nuestras pruebas unitarias, para verificar que el código probado realmente hace lo que creemos que hace, estoy totalmente de acuerdo.
Péter Török
3

Si no sabe qué hace una función, no puede escribir una prueba unitaria para ella. Por lo que sabes, ni siquiera hace lo que se supone que debe hacer. Debes averiguar qué se supone que debe hacer primero. ENTONCES escriba la prueba.

Edward extraño
fuente
3

En el mundo real, es perfectamente normal escribir pruebas unitarias para el código de otra persona. Claro, el desarrollador original ya debería haber hecho esto, pero a menudo recibe código heredado donde esto simplemente no se hizo. Por cierto, no importa si ese código heredado vino hace décadas de una galaxia muy, muy lejana, o si uno de tus compañeros de trabajo lo revisó la semana pasada, o si lo escribiste hoy, el código heredado es código sin pruebas

Pregúntese: ¿por qué escribimos pruebas unitarias? Going Green obviamente es solo un medio para un fin, el objetivo final es demostrar o refutar las afirmaciones sobre el código que se está probando.

Digamos que tiene un método que calcula la raíz cuadrada de un número de coma flotante. En Java, la interfaz lo definiría como:

public double squareRoot(double number);

No importa si escribió la implementación o si alguien más lo hizo, desea afirmar algunas propiedades de squareRoot:

  1. que puede devolver raíces simples como sqrt (4.0)
  2. que puede encontrar una raíz real como sqrt (2.0) con una precisión razonable
  3. que encuentra que sqrt (0.0) es 0.0
  4. que arroja una IllegalArgumentException cuando se alimenta un número negativo, es decir, en sqrt (-1.0)

Entonces comienzas a escribir esto como pruebas individuales:

@Test
public void canFindSimpleRoot() {
  assertEquals(2, squareRoot(4), epsilon);
}

Vaya, esta prueba ya falla:

java.lang.AssertionError: Use assertEquals(expected, actual, delta) to compare floating-point numbers

Te olvidaste de la aritmética de coma flotante. OK, presentas double epsilon=0.01y listo:

@Test
public void canFindSimpleRootToEpsilonPrecision() {
  assertEquals(2, squareRoot(4), epsilon);
}

y agregue las otras pruebas: finalmente

@Test
@ExpectedException(IllegalArgumentException.class)
public void throwsExceptionOnNegativeInput() {
  assertEquals(-1, squareRoot(-1), epsilon);
}

y vaya, otra vez:

java.lang.AssertionError: expected:<-1.0> but was:<NaN>

Deberías haber probado:

@Test
public void returnsNaNOnNegativeInput() {
  assertEquals(Double.NaN, squareRoot(-1), epsilon);
}

Que hemos hecho aqui Comenzamos con algunas suposiciones sobre cómo debería comportarse el método, y descubrimos que no todas eran ciertas. Luego hicimos el conjunto de pruebas Verde, para anotar la prueba de que el método se comporta de acuerdo con nuestros supuestos corregidos. Ahora los clientes de este código pueden confiar en este comportamiento. Si alguien cambiara la implementación real de squareRoot con algo más, algo que, por ejemplo, realmente arrojó una excepción en lugar de devolver NaN, nuestras pruebas detectarían esto de inmediato.

Este ejemplo es trivial, pero a menudo hereda grandes fragmentos de código donde no está claro lo que realmente hace. En ese caso, es normal colocar un arnés de prueba alrededor del código. Comience con algunos supuestos básicos sobre cómo debe comportarse el código, escriba pruebas unitarias para ellos, pruebe. Si es verde, bien, escribe más pruebas. Si es rojo, bueno, ahora tiene una afirmación fallida de que puede mantener una especificación. Tal vez hay un error en el código heredado. Tal vez la especificación no está clara sobre esta entrada en particular. Tal vez no tienes una especificación. En ese caso, vuelva a escribir la prueba de modo que documente el comportamiento inesperado:

@Test
public void throwsNoExceptionOnNegativeInput() {
  assertNotNull(squareRoot(-1)); // Shouldn't this fail?
}

Con el tiempo, terminas con un arnés de prueba que documenta cómo se comporta realmente el código y se convierte en una especie de especificación codificada. Si alguna vez desea cambiar el código heredado, o reemplazarlo con otra cosa, tiene el arnés de prueba para verificar que el nuevo código se comporta de la misma manera, o que el nuevo código se comporta de manera diferente en las formas esperadas y controladas (por ejemplo, que realmente corrige el error que esperas que solucione). Este arnés no tiene que estar completo el primer día, de hecho, tener un arnés incompleto es casi siempre mejor que no tener ningún arnés. Tener un arnés significa que puede escribir su código de cliente con más facilidad, sabe dónde esperar que las cosas se rompan cuando cambia algo, y dónde se rompieron cuando finalmente lo hicieron.

Debe tratar de salir de la mentalidad de que tiene que escribir pruebas unitarias solo porque tiene que hacerlo, como si completara los campos obligatorios en un formulario. Y no debe escribir pruebas unitarias solo para que la línea roja sea verde. Las pruebas unitarias no son tus enemigos, las pruebas unitarias son tus amigos.

Wallenborn
fuente
1

Cuando escribo casos de prueba (para impresoras) trato de pensar en cada pequeño componente ... y qué puedo hacer para romperlo. Digamos, por ejemplo, el escáner, qué comandos usa (en el lenguaje de trabajo de la impresora pjl) qué puedo escribir para probar cada bit de funcionalidad ... Ahora bien, ¿qué puedo hacer para intentar romper eso?

Intento hacer eso para cada uno de los componentes principales, pero cuando se trata de software y no tanto de hardware, desea observar cada método / función y verificar los límites y demás.


fuente
1

Parece que está trabajando con otros desarrolladores (o mantiene código escrito por otros desarrolladores) que no realizan pruebas unitarias. En ese caso, creo que definitivamente querrá saber qué se supone que debe hacer el objeto o método que está probando, luego cree una prueba para ello.

No será TDD porque no escribiste la prueba primero, pero podrías mejorar la situación. También es posible que desee crear una copia de los objetos bajo prueba con apéndices para que pueda establecer que sus pruebas funcionan correctamente cuando falla el código.

vjones
fuente