2009-06-20 27 views
79

En cuanto al patrón de prueba clásico 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.¿Debería ser "Arrange-Assert-Act-Assert"?

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 una diferencia Si escribo una prueba de aprobación, entonces cualquier código lo 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.

¿Sus pruebas siguen este patrón? ¿Por qué o por qué no?

Actualización Aclaración: la afirmación inicial es esencialmente lo contrario de 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ó.

Respuesta

7

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 volver simplemente verdad. 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, que includes() funcione, pero quizás no. Entonces, la primera afirmación es un control de cordura, para asegurar que la segunda afirmación sea realmente significativa.

Lea por sí mismo, assertTrue(range.includes(7)); dice: "afirme que el rango modificado incluye 7". Leído en el contexto de la primera aserción, está diciendo: "afirmar que invocando abarcar() hace que incluya 7. Y dado que abarca es la unidad que estamos probando, creo que es de algún (pequeño) valor.

estoy aceptando mi propia respuesta;. una gran cantidad de los otros malinterpretado mi pregunta para ser sobre cómo probar la configuración Creo que esto es un poco diferente

+0

Gracias por regresar con un ejemplo, Carl. Bueno, en la parte roja del ciclo TDD, hasta que abarque() realmente hace algo; la primera afirmación no tiene sentido, es solo una duplicación de la segunda. En verde, comienza a ser útil. Está adquiriendo sentido durante la refactorización. Podría ser bueno tener un marco UT que lo haga automáticamente. – philant

+0

Supongamos que usa TDD en la clase Range, ¿no habrá otra prueba fallida probando el Range Ctor, cuando la romperá? – philant

+1

@philippe: No estoy seguro de entender la pregunta. El constructor de Range e includes() tienen sus propias pruebas unitarias. ¿Podría elaborar, por favor? –

0

Depende del entorno/idioma de prueba, pero normalmente 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 asegurarse de que no se ganó t falla (sin lanzar una excepción).

1

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 anterior. 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.

104

Esto no es lo más común de 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 valida cualquier precondición que sienta la necesidad de garantizar. 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 deben cumplirse para un caso de prueba dado, por lo que puede necesitar más de una declaración de 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.

+0

+1, muy buena respuesta. La última parte es especialmente importante, porque muestra que puedes guardar cosas como una prueba de unidad separada. – murrekatt

+2

En general, también lo he hecho de esta manera, pero hay un problema con una prueba separada para garantizar las condiciones previas (especialmente con una gran base de código con requisitos cambiantes): la prueba de condición previa se modificará con el tiempo y no se sincronizará con la principal. 'prueba que presupone esas condiciones previas. Entonces, las condiciones previas pueden ser todas buenas y verdes, pero esas condiciones previas no están satisfechas en la prueba principal, que ahora siempre muestra verde y fina. Pero si las condiciones previas estuvieran en la prueba principal, habrían fallado. ¿Has encontrado este problema y encuentras una buena solución para eso? – nchaud

+2

Si cambia sus pruebas mucho, puede [tener otros problemas] (http://blog.ploeh.dk/2013/04/02/why-trust-tests), porque eso tenderá a hacer que sus pruebas sean menos confiable. Incluso frente a los requisitos cambiantes, considere [el diseño de código de forma adjunta únicamente] (http://blog.ploeh.dk/2012/01/03/SOLIDisAppend-only). –

1

Ya he leído acerca de esta técnica, posiblemente de usted por cierto, 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 se escribe la prueba? ¿Se produce un error en esta afirmación, siguiendo un ciclo de refactorización rojo, verde, rojo y verde, o se agrega? 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.

+0

Normalmente no obligo a la afirmación inicial a fallar; después de todo, no debería fallar, como debería ser una afirmación TDD, antes de escribir su método. Yo lo escribo, cuando lo escribo, antes, en el curso normal de la escritura, no después. Honestamente, no recuerdo haber fallado, tal vez eso sugiere que es una pérdida de tiempo. Trataré de dar un ejemplo, pero no tengo uno en mente en este momento. Gracias por las preguntas; son útiles. –

1

he hecho esto antes cuando se investiga una prueba que no

..

Después de un considerable arañazo en la cabeza, determiné que la causa era que los métodos llamados durante "Arreglar" no funcionaban correctamente. La falla de la prueba fue engañosa. Afirma después del arreglo. Esto hizo que la prueba fallara en un lugar que resaltaba el problema real.

Creo que aquí también hay un olor a código si la parte Arrange de la prueba es demasiado larga y complicada.

+0

Un punto menor: consideraría demasiado complicado Arregle más un olor de diseño que un olor de código: a veces el diseño es tal que solo un Arrange complicado le permitirá probar la unidad. Lo menciono porque esa situación quiere una solución más profunda que un simple olor a código. –

25

También se puede especificar como Arrange- Suponer -Act-Assert.

, existe un asa para esta técnica en NUnit, como en el ejemplo aquí: http://nunit.org/index.php?p=theory&r=2.5.7

+1

¡Agradable! Me gusta un cuarto - y diferente - y preciso - "A". ¡Gracias! –

+0

+1, @Ole! ¡Me gusta este, también, para ciertos casos especiales! ¡Lo probaré! –

1

En general, me gusta "Organizar, Ley, afirman" mucho y lo utilizan como mi nivel 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 está one of those developers who does not use TearDown or TestFixtureTearDown para limpiar 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?

+0

Annul?No exactamente correcto, pero cercano. –

+1

@carlmanaster, ¡en realidad estás lo suficientemente cerca de mí! Voy a pegar eso en mi próximo TestFixture para probarlo para el tamaño. Es como ese pequeño recordatorio de hacer lo que tu madre debería haberte enseñado: "¡Si lo abres, ciérralo! ¡Si lo estropeas, límpialo!" Tal vez alguien más pueda mejorar, pero al menos comienza con un "¡a!" ¡Gracias por tu sugerencia! –

+1

@carlmanaster, le di una oportunidad a "Annul". Es mejor que "desmontar", y funciona, pero sigo buscando otra palabra "A" que quede en mi cabeza tan perfectamente como "Organizar, Actuar, Afirmar". Tal vez "Annihilate ?!" –

1

Ahora estoy haciendo esto. A-Un-A-A 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 por lo que la Ley no contiene la lectura de la ReadUpdated se debe a que 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.

0

yo no uso ese patrón, porque creo que hacer algo como:

Arrange 
Assert-Not 
Act 
Assert 

Puede ser inútil, porque supuestamente sabe que su Organizar parte funciona correctamente, lo que significa que lo que está en la parte Organizar debe ser probado también o ser lo suficientemente simple como para no necesitar pruebas.

Usando el ejemplo de su 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)); 
} 
+0

Me temo que realmente no entiendo mi pregunta. La afirmación inicial no se trata de probar Arrange; es simplemente asegurar que la Ley sea lo que provoque que se afirme el estado al final. –

+0

Y mi punto es que, sea lo que sea que pongas en la parte Assert-Not, ya está implícito en la parte Arrange, porque el código en la parte Arrange está completamente probado y ya sabes lo que hace. –

+0

Pero creo que hay valor en la parte Assert-Not, porque usted dice: Dado que la parte Arrange deja 'el mundo' en 'este estado' entonces mi 'Act' dejará 'el mundo' en este 'nuevo estado' '; y si la implementación del código de la que depende la parte de Arrange cambia, entonces la prueba también se romperá. Pero, de nuevo, eso podría estar en contra de DRY, porque usted (debería) también debe tener pruebas para cualquier código del que dependa en la parte Arrange. –

1

Tener un vistazo a la entrada de Wikipedia sobre 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. A-A-A es un recordatorio útil para los pasos mínimos requeridos, pero no debe desalentar a nadie para que cree pasos adicionales.

4

Una prueba Arrange-Assert-Act-Assert siempre puede ser reprogramado para utilizar dos pruebas:

1. Arrange-Assert 
2. Arrange-Act-Assert 

Esto tiene la ventaja de dar una retroalimentación más precisa sobre si se trata de la Organizar o la Ley que falló, sin tener que ejecutar un depurador en el 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.

0

Si realmente desea probar todo en el ejemplo, trate de 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 echa en falta tantas posibilidades de errores ... por ejemplo, después abarcan, la de este producto 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 tratar de abarcar 5 en el rango ... ¿qué haríamos? ¿espera una excepción en englobar o el rango no alterado?

De todos modos, el punto es si hay alguna suposición en el acto que desea probar, póngalos en su propia prueba, ¿sí?

0

que utilizo:

1. Setup 
2. Act 
3. Assert 
4. Teardown 

Debido a que una instalación limpia es muy importante.

Cuestiones relacionadas