unit testing - ¿Debería ser "Arrange-Assert-Act-Assert"?
unit-testing language-agnostic (14)
Ahora estoy haciendo esto AAAA de un tipo diferente
Arrange - setup
Act - what is being tested
Assemble - what is optionally needed to perform the assert
Assert - the actual assertions
Ejemplo de una prueba de actualización:
Arrange:
New object as NewObject
Set properties of NewObject
Save the NewObject
Read the object as ReadObject
Act:
Change the ReadObject
Save the ReadObject
Assemble:
Read the object as ReadUpdated
Assert:
Compare ReadUpdated with ReadObject properties
La razón es para que el ACT no contenga la lectura de ReadUpdated porque no es parte del acto. El acto solo está cambiando y salvando. Entonces realmente, ARRANGE ReadUpdated para aserción, estoy llamando a ASSEMBLE para la aserción. Esto es para evitar confundir la sección ARRANGE
ASSERT solo debe contener aserciones. Eso deja ASSEMBLE entre ACT y ASSERT que configura la afirmación.
Por último, si está fallando en el arreglo, sus pruebas no son correctas porque debe tener otras pruebas para prevenir / encontrar estos errores triviales . Porque para el escenario que presento, ya debe haber otras pruebas que prueban LEER y CREAR. Si crea una "Aserción de Guardia", puede estar rompiendo DRY y creando mantenimiento.
Con respecto al clásico patrón de prueba de Arrange-Act-Assert , frecuentemente me encuentro agregando una contraafirmación que precede a Act. De esta forma, sé que la afirmación que pasa realmente está pasando como resultado de la acción.
Creo que es análogo al rojo en rojo-verde-refactor, donde solo si he visto la barra roja en el curso de mis pruebas, sé que la barra verde significa que he escrito un código que hace la diferencia. Si escribo una prueba de aprobación, cualquier código la satisfará; de manera similar, con respecto a Arrange-Assert-Act-Assert, si mi primera afirmación falla, sé que cualquier Ley habría pasado el Assert final, por lo que no estaba realmente verificando nada sobre la Ley.
¿Tus exámenes siguen este patrón? ¿Por qué o por qué no?
Clarificación de la actualización : la afirmación inicial es esencialmente lo opuesto a la afirmación final. No es una afirmación de que Arrange funcionó; es una afirmación de que la Ley aún no funcionó.
Aquí hay un ejemplo.
public void testEncompass() throws Exception {
Range range = new Range(0, 5);
assertFalse(range.includes(7));
range.encompass(7);
assertTrue(range.includes(7));
}
Podría ser que escribí Range.includes()
para simplemente devolver true. No lo hice, pero puedo imaginar que podría haberlo hecho. O podría haber escrito mal de muchas otras maneras. Espero y espero que con TDD realmente lo haya hecho bien, eso includes()
simplemente funciona, pero tal vez no. Entonces, la primera afirmación es un control de cordura, para asegurar que la segunda afirmación sea realmente significativa.
Leer solo, assertTrue(range.includes(7));
está diciendo: "afirmar que el rango modificado incluye 7". Leído en el contexto de la primera afirmación, está diciendo: "afirmar que invocar abarcar () hace que incluya 7. Y dado que abarcar es la unidad que estamos probando, creo que tiene algún valor (pequeño).
Estoy aceptando mi propia respuesta; muchos de los otros malinterpretaron mi pregunta acerca de probar la configuración. Creo que esto es ligeramente diferente.
Depende de su entorno / lenguaje de prueba, pero generalmente si algo de la parte Arrange falla, se lanza una excepción y la prueba falla mostrándola en lugar de iniciar la parte Act. Entonces no, normalmente no uso una segunda parte de Assert.
Además, en el caso de que su parte de Arrange sea bastante compleja y no siempre arroje una excepción, quizás deba considerar envolverla dentro de algún método y escribir una prueba propia para que así pueda estar seguro de que no fallará (sin lanzando una excepción).
Eche un vistazo a la entrada de Wikipedia en Design by Contract . La trinidad santa Arrange-Act-Assert es un intento de codificar algunos de los mismos conceptos y se trata de probar la corrección del programa. Del artículo:
The notion of a contract extends down to the method/procedure level; the
contract for each method will normally contain the following pieces of
information:
Acceptable and unacceptable input values or types, and their meanings
Return values or types, and their meanings
Error and exception condition values or types that can occur, and their meanings
Side effects
Preconditions
Postconditions
Invariants
(more rarely) Performance guarantees, e.g. for time or space used
Existe una compensación entre la cantidad de esfuerzo dedicado a configurar esto y el valor que agrega. AAA es un recordatorio útil para los pasos mínimos requeridos, pero no debe desalentar a nadie para que cree pasos adicionales.
En general, me gusta mucho "Arrange, Act, Assert" y lo uso como mi estándar personal. Sin embargo, lo único que no me recuerda que haga es disolver lo que he organizado cuando se hacen las afirmaciones. En la mayoría de los casos, esto no causa mucha molestia, ya que la mayoría de las cosas desaparecen automáticamente a través de la recolección de basura, etc. Sin embargo, si ha establecido conexiones con recursos externos, probablemente quiera cerrar esas conexiones cuando haya terminado. con sus afirmaciones o usted tiene un servidor o un recurso costoso en algún lugar aferrándose a conexiones o recursos vitales que debería poder regalar a otra persona. Esto es particularmente importante si usted es uno de los desarrolladores que no usa TearDown o TestFixtureTearDown para realizar una limpieza después de una o más pruebas. Por supuesto, "Arrange, Act, Assert" no es responsable de mi falla al cerrar lo que abro; Solo menciono este "gotcha" porque aún no he encontrado un buen sinónimo de "A-word" para "dispose" para recomendarlo. ¿Alguna sugerencia?
Esto no es lo más común que se puede hacer, pero sigue siendo lo suficientemente común como para tener su propio nombre. Esta técnica se llama Guard Assertion . Puede encontrar una descripción detallada en la página 490 en el excelente libro xUnit Test Patterns de Gerard Meszaros (muy recomendado).
Normalmente, no utilizo este patrón yo mismo, ya que me parece más correcto escribir una prueba específica que valide cualquier precondición que sienta la necesidad de asegurar. Tal prueba siempre debería fallar si la precondición falla, y esto significa que no la necesito incrustada en todas las otras pruebas. Esto proporciona un mejor aislamiento de las preocupaciones, ya que un caso de prueba solo verifica una cosa.
Puede haber muchas condiciones previas que se deben satisfacer para un caso de prueba determinado, por lo que es posible que necesite más de una declaración de la Guardia. En lugar de repetir eso en todas las pruebas, tener una (y una sola) prueba para cada precondición mantiene el código de prueba más sostenible, ya que tendrá menos repetición de esa manera.
Lanzar una afirmación de "comprobación de cordura" para verificar el estado antes de realizar la acción que está probando es una técnica antigua. Normalmente los escribo como un andamio de prueba para probarme a mí mismo que la prueba hace lo que espero, y eliminarlos más tarde para evitar las pruebas de desorden con los andamios de prueba. A veces, dejar el andamio ayuda a que la prueba sirva de narrativa.
Lo he hecho antes cuando investigaba una prueba que falló.
Después de un considerable rasguño en la cabeza, determiné que la causa era que los métodos llamados durante "Arrange" no funcionaban correctamente. La falla de la prueba fue engañosa. Agregué un Assert después del arreglo. Esto hizo que la prueba fallara en un lugar que resaltaba el problema real.
Creo que también hay un olor a código aquí si la parte Arrange de la prueba es demasiado larga y complicada.
No uso ese patrón porque creo que hago algo como:
Arrange
Assert-Not
Act
Assert
Puede ser inútil, porque supuestamente usted sabe que su parte Arrange funciona correctamente, lo que significa que todo lo que está en la parte Arrange debe probarse o ser lo suficientemente simple como para no necesitar pruebas.
Usando el ejemplo de tu respuesta:
public void testEncompass() throws Exception {
Range range = new Range(0, 5);
assertFalse(range.includes(7)); // <-- Pointless and against DRY if there
// are unit tests for Range(int, int)
range.encompass(7);
assertTrue(range.includes(7));
}
Si realmente quieres probar todo en el ejemplo, prueba más pruebas ... como:
public void testIncludes7() throws Exception {
Range range = new Range(0, 5);
assertFalse(range.includes(7));
}
public void testIncludes5() throws Exception {
Range range = new Range(0, 5);
assertTrue(range.includes(5));
}
public void testIncludes0() throws Exception {
Range range = new Range(0, 5);
assertTrue(range.includes(0));
}
public void testEncompassInc7() throws Exception {
Range range = new Range(0, 5);
range.encompass(7);
assertTrue(range.includes(7));
}
public void testEncompassInc5() throws Exception {
Range range = new Range(0, 5);
range.encompass(7);
assertTrue(range.includes(5));
}
public void testEncompassInc0() throws Exception {
Range range = new Range(0, 5);
range.encompass(7);
assertTrue(range.includes(0));
}
Porque de lo contrario, se pierden tantas posibilidades de error ... por ejemplo, después de englobar, el rango solo incluye 7, etc. También hay pruebas de la duración del rango (para garantizar que no abarque también un valor aleatorio), y otro conjunto de pruebas completamente para intentar abarcar 5 en el rango ... ¿qué esperaríamos - una excepción en abarcar, o el rango para ser inalterado?
De todos modos, el punto es si hay alguna suposición en el acto que desea probar, póngalos en su propia prueba, ¿sí?
También se podría especificar como Arrange-Assume -Act-Assert.
Hay un manejo técnico para esto en NUnit, como en el ejemplo aquí: http://nunit.org/index.php?p=theory&r=2.5.7
Una prueba Arrange-Assert-Act-Assert
siempre se puede refactorizar en dos pruebas:
1. Arrange-Assert
2. Arrange-Act-Assert
Esto tiene la ventaja de proporcionar una retroalimentación más precisa sobre si es el arreglo o la ley que falló, sin tener que ejecutar un depurador en la prueba original. También satisface la intención de una prueba unitaria mejor, ya que está separando su prueba en unidades independientes más pequeñas.
También tenga en cuenta que cada vez que vea secciones Arrange similares en diferentes pruebas, debe intentar extraerlas en métodos de ayuda compartidos, para que sus pruebas sean más DRY y más DRY mantener en el futuro.
Ya leí sobre esta técnica, posiblemente de usted, pero no la uso; sobre todo porque estoy acostumbrado a la forma triple A para las pruebas de mi unidad.
Ahora, tengo curiosidad y tengo algunas preguntas: ¿cómo escribes tu prueba, causas que falle esta afirmación, siguiendo un ciclo de refactor rojo-verde-rojo-verde, o lo agregas después?
¿Fallas a veces, quizás después de refactorizar el código? Qué te dice esto ? Quizás podría compartir un ejemplo en el que ayudó. Gracias.
Yo suelo:
1. Setup
2. Act
3. Assert
4. Teardown
Porque una configuración limpia es muy importante.