2010-06-30 10 views
6

Tengo un objeto de servicio no trivial desarrollado con TDD. Comenzó con una tarea simple: para un objeto de la cola, construya un intento de procesamiento asíncrono. Así que escribí una prueba alrededor de mi método constructAttempt():TDD - ¿Refactorizar en caja negra?

void constructAttempt() {...} 

Hay numerosos escenarios posibles que deben tenerse en cuenta, así que tengo una docena de pruebas para este método.


Luego implementé lo que realmente necesitaba: Escanear toda la cola y construir un lote de intentos. Así que el código se parece más a:

public void go() { 
    for (QueuedItem item : getQueuedItems()) { 
     constructAttempt(item); 
    } 
} 

por lo que añade una nueva prueba o dos para este método go().


Finalmente descubrí que necesitaba un poco de pre-procesamiento que a veces puede afectar constructAttempt(). Ahora el código se parece más a:

public void go() { 
    preprocess(); 
    for (QueuedItem item : getQueuedItems()) { 
     constructAttempt(item); 
    } 
} 

Tengo algunas dudas sobre lo que debería hacer ahora.

¿Debo mantener el código como está, con constructAttempt(), preprocess() y go() probado independientemente? ¿Por qué sí/por qué no? Me arriesgo a no cubrir los efectos secundarios del preprocesamiento y romper la encapsulación.

¿O debo refactorizar todo mi conjunto de pruebas para que solo llame al go() (que es el único método público)? ¿Por qué sí/por qué no? Esto haría las pruebas un poco más oscuras, pero por otro lado tomaría todas las interacciones posibles en consideración. De hecho, se convertiría en una prueba de recuadro negro utilizando solo la API pública, lo que puede no estar en línea con TDD.

+0

Si refactoriza su conjunto de pruebas para probar solo el método go() ... ¿cómo identificará si los errores detectados están en preprocess() o buildAttempt? –

Respuesta

6

El método go realmente está orquestando varias interacciones, y no es muy interesante en sí mismo. Si escribe sus pruebas contra go en lugar de sus métodos subordinados, las pruebas probablemente sean horriblemente complicadas porque tendrá que dar cuenta de la explosión combinatoria de interacciones entre preprocess y constructAttempt (y tal vez incluso getQueuedItems, aunque eso suena relativamente simple).

En su lugar, debe escribir pruebas para los métodos subordinados, y las pruebas para constructAttempt deben tener en cuenta todos los efectos potenciales del preproceso. Si no puede simular esos efectos secundarios (manipulando la cola subyacente o una prueba doble), refactorice su clase hasta que pueda.

1

Digo que su suite de pruebas solo llame a go() ya que es la única API pública. Lo que significa que una vez que haya cubierto todos los escenarios para el método go (que incluiría el preproceso y la cola), entonces ya no importa si cambia la implementación interna. Su clase se mantiene correcta en lo que respecta al uso público.

Espero que esté utilizando la inversión de la dependencia para las clases utilizadas para las tareas de preprocesamiento/cola, de esa manera puede probar independientemente el preprocesamiento y luego simularlo en su prueba go().

3

@Jeff lo tiene bien. Lo que realmente tienes son dos responsabilidades que ocurren en este objeto. Es posible que desee colocar los artículos en cola en su propia clase.Presione preprocess y constructAttempt en elementos individuales. En mi humilde opinión, cuando tienes una clase que trata con elementos individuales y una lista de artículos que huele un código. Las responsabilidades son los actos de contenedor de lista en los artículos.

public void go() { 
    for (QueuedItem item : getQueuedItems()) { 
     item.preprocess(); 
     item.constructAttempt(); 
    } 
} 

Nota: Esto es similar a trabajar con un patrón objeto de comando

[EDIT 1a] Esto hace que sea muy fácil de probar con burla. El método go solo necesita pruebas con un solo elemento de cola o sin elementos de cola. También cada item ahora puede tener sus pruebas individuales separadas de la explosión combinatoria del go.

[1b EDIT] que incluso podría ser capaz de doblar la preprocess en el tema:

public void go() { 
    for (QueuedItem asyncCommunication: getQueuedItems()) { 
     asyncCommunication.attempt(); 
    } 
} 

Ahora usted tienen un verdadero command pattern.