2012-09-24 17 views
5

Supongamos la siguiente implementación del método de plantilla:unidad de prueba del patrón de diseño método plantilla

public abstract class Abs 
    { 
    void DoYourThing() 
    { 
     log.Info("Starting"); 
     try 
     { 
     DoYourThingInternal(); 
     log.Info("All done"); 
     } 
     catch (MyException ex) 
     { 
     log.Error("Something went wrong here!"); 
     throw; 
     } 
    } 

    protected abstract void DoYourThingInternal(); 

    } 

Ahora, hay un montón de información en torno a la manera de probar la clase Abs, y asegúrese de que DoYourThingInternal se llama.
Sin embargo, supongamos que quiero probar mi clase Conc:

public class Conc : Abs 
    { 
    protected override void DoYourThingInternal() 
    { 
     // Lots of awesome stuff here! 
    } 
    } 

no me gustaría hacer conc.DoYourThing(), ya que este invocará el método de los padres, que ya fue probado por separado.

Me gustaría probar solo el método de anulación.

¿Alguna idea?

Respuesta

2

No consideraría que DoYourThingInternal() fuera de DoYourThing() (como en, dos módulos separados de código que se pueden probar de forma aislada) ya que de todos modos no podrá instanciar su clase abstracta y los 2 métodos lo harán siempre corre juntos. Además, DoYourThingInternal() tiene acceso a todos los miembros protegidos de su clase y podría modificarlos, con posibles efectos secundarios en DoYourThing(). Por lo tanto, creo que sería peligroso probar DoYourThing() en completo aislamiento de una implementación concreta de DoYourThingInternal().

Sin embargo, eso no significa que no pueda tener pruebas separadas para el comportamiento esperado de DoYourThing(), que debe permanecer igual en todas las implementaciones de Abs y en el comportamiento esperado de DoYourThingInternal().

Puede usar una clase de prueba base (abstracta) donde defina una prueba para el comportamiento general esperado de DoYourThing(). A continuación, cree tantas subclases de prueba como implementaciones de Abs, con pruebas unitarias para los detalles de cada implementación.

La prueba de la clase de prueba base será heredado, y cuando se ejecutan pruebas de cualquier subclase, la prueba heredada de DoYourThing() también se ejecutará:

public abstract class AbsBaseTest 
{ 
    public abstract Abs GetAbs(); 

    [Test] 
    public void TestSharedBehavior() 
    { 
    getAbs().DoYourThing(); 

    // Test shared behavior here... 
    } 
} 

[TestFixture] 
public class AbsImplTest : AbsBaseTest 
{ 
    public override Abs GetAbs() 
    { 
    return new AbsImpl(); 
    } 

    [Test] 
    public void TestParticularBehavior() 
    { 
    getAbs().DoYourThing(); 

    // Test specific behavior here 
    } 
} 

Ver http://hotgazpacho.org/2010/09/testing-pattern-factory-method-on-base-test-class/

No sabe si la herencia de la clase de prueba abstracta es soportada por todos los frameworks de pruebas unitarias (creo que NUnit sí).

0

¿Qué tal pegar una interfaz en Abs y burlarse de ella? ¿Ignorando las llamadas o estableciendo expectativas sobre ellas?

+0

no estoy seguro I understand; podrías elaborar? –

3

Supongo que parte del "problema" es que no hay forma de llamar a un método protegido desde fuera de la clase. ¿Qué hay de una clase que deriva de simulacro Conc y proporciona un nuevo método público:

public class MockConc: Conc 
    { 
    void MockYourThingInternal() 
    { 
     DoYourThingInternal() 
    } 
    } 
2

Has marcado la pregunta "TDD" pero dudo que haya seguido ese principio cuando surgió este "problema".

Si realmente seguido TDD su flujo de trabajo habría sido algo así como

  1. escribir una prueba cierta lógica todavía no se han aplicado
  2. Impl la impl más fácil posible para esta prueba para que sea verde (la lógica de Conc1 por ejemplo)
  3. Refactor
  4. escribir una prueba por alguna otra lógica todavía no se han aplicado
  5. impl la impl más fácil posible para esta prueba para que sea verde (la lógica de Conc2 para el correo jemplo)
  6. Refactor

En "6" se podría pensar que sería una gran idea para poner en práctica un método de la plantilla porque Conc1 y Conc2 comparten cierta lógica. Simplemente hazlo y ejecuta tus pruebas para ver que la lógica aún funciona.

Escribir pruebas para verificar la lógica, no basarlas en cómo se ve la implementación (= comenzar con las pruebas). En este caso, comience a escribir pruebas verificando que la lógica funciona (la lógica luego se coloca en sus tipos concretos). Sí, eso significa que algunas líneas de código (la de su clase abstracta) se prueban varias veces. ¿Y qué? Uno de los puntos de las pruebas escritas es que debería poder refactorizar su código pero aún así poder verificar que funciona ejecutando sus pruebas. Si luego no desea utilizar el patrón de método de la plantilla, en un mundo ideal, no debería necesitar cambiar ninguna prueba, sino simplemente cambiar la implementación.

Si empiezas a pensar qué líneas de código pruebas, IMO pierdes muchos de los beneficios de escribir pruebas. Desea asegurarse de que su lógica funcione; escriba pruebas para esto en su lugar.

+1

Veo lo que quieres decir. Y tiene sentido. Sin embargo, si lo hago, como sugirió, pruebe algunas líneas de código varias veces, eso significa que si cambio esas líneas de código, necesito cambiar varias pruebas. Y ese podría ser un alto precio a pagar ... –

+3

(asumiendo que estamos hablando de TDD ahora ...) No solo cambia las líneas de código. Agrega una nueva funcionalidad que significa agregar una o muchas pruebas nuevas. Para hacer que estos pasen, cambie el código implementado. Si eso significa que esto rompe las pruebas anteriores, significa que algunas especificaciones/pruebas antiguas ya no son válidas. Y eso es lo bueno de tener pruebas en primer lugar -> ahora tienes la oportunidad de saber que tu nuevo código cambió el comportamiento anterior. ¿El nuevo código estaba rompiendo el código anterior? ¿Las antiguas pruebas/especificaciones ya no son válidas? ¿Necesita preguntarle a su cliente/gerente de producto "¿qué pasa con la especificación anterior? ¿Ya no es válida?" – Roger

+1

Muy buen punto. sin duda, me resulta difícil pensar con una mentalidad 'TDD' ... –

0

Puede hacerlo de varias maneras, muchas de las cuales ya están documentadas aquí. Este es el enfoque que normalmente tomo: hacer que el caso de prueba herede de la clase concreta.

public ConcTest : Conc 
{ 
    [Test] 
    public void DoesItsThingRight() 
    { 
     var Result = DoItsThingInternal(); 
     // Assert the response 
    } 
} 

Hope that helps!

Brandon