2012-02-20 9 views
7

Estoy escribiendo una serie de clases de colección en C#, cada una de las cuales implementa interfaces personalizadas similares. ¿Es posible escribir una única colección de pruebas unitarias para una interfaz y ejecutarlas automáticamente en varias implementaciones diferentes? Me gustaría evitar cualquier código de prueba duplicado para cada implementación.¿Puedo implementar una serie de pruebas reutilizables para probar la implementación de una interfaz?

Estoy dispuesto a buscar en cualquier marco (NUnit, etc.) o extensión de Visual Studio para lograr esto.


Para aquellos que buscan hacer lo mismo, he publicado mi solución concreta, con sede fuera de avandeursen's accepted solution, como an answer.

+1

Agregó [lsp] (http://stackoverflow.com/questions/tagged/lsp) como etiqueta, ya que la pregunta y la respuesta se aplican a cualquier jerarquía de clases que se adhiera a LSP. – avandeursen

Respuesta

6

Sí, eso es posible. El truco es permitir que la jerarquía de pruebas de la clase de su unidad siga la jerarquía de clases de su código.

Supongamos que tiene una interfaz Itf con clases de implementación C1 y C2.

Primero crea una clase de prueba para Itf (ItfTest). Para realmente ejercitar la prueba, debe crear una implementación simulada de su interfaz Itf.

Todas las pruebas en este ItfTest deben pasar en cualquier implementación de Itf (!). Si no, su aplicación no se ajusta a la Liskov Substitution Principle (la "L" en SOLID principios de Martin de diseño orientado a objetos)

Por lo tanto, para crear un caso de prueba para C1, la clase C1Test puede extender ItfTest. Su extensión debe reemplazar la creación del objeto simulado con la creación de un objeto C1 (inyectándolo en, o usando un GoF factory method). De esta forma, todos los casos ItfTest se aplican a instancias del tipo C1. Además, su clase C1Test puede contener casos de prueba adicionales específicos para C1.

Del mismo modo para C2. Y puede repetir el truco para clases e interfaces anidadas más profundas.

Referencias: Binder's Polymorphic Server Test patrón, y McGregor's PACT - Arquitectura paralela para pruebas de componentes.

+1

Para evitar implementaciones simuladas, simplemente declare mi clase 'ItfTest' como abstracta y la declaro como 'protegida como Itf CreateInstance();'. (Tenga en cuenta que tanto 'ItfTest' como' C1Test' necesitan tener el atributo '[TestClass]'.) – dlras2

+1

Sí, también utilicé el método de fábrica, ya que es más simple. Usé este enfoque de prueba en JUnit, donde no existe el atributo '[TestClass]', pero donde las anotaciones '@ Test' en las superclases se heredan, lo que hace que los casos de prueba de la superclase se vuelvan a ejecutar en subclases. Y: gracias por la edición. – avandeursen

2

Puede usar los atributos [RowTest] en MBUnit para hacerlo. El ejemplo siguiente muestra donde se pasa el método de una cadena para indicar qué clase de implementación de interfaz que desea crear una instancia, y luego crea esta clase a través de la reflexión:

[RowTest] 
[Row("Class1")] 
[Row("Class2")] 
[Row("Class3")] 
public void TestMethod(string type) 
{ 
    IMyInterface foo = Activator.CreateInstance(Type.GetType(type)) as IMyInterface; 

    //Do tests on foo: 
} 

En el [fila] atributos, puede pasar cualquier número arbitrario de parámetros de entrada, como los valores de entrada para la prueba o los valores esperados que se devolverán mediante invocaciones de métodos. Deberá agregar argumentos de los tipos correspondientes como argumentos de entrada de método de prueba.

+0

¿Puedes editar tu respuesta para explicar exactamente cómo probaría las implementaciones de esta manera? Pareces estar diciendo que cada prueba unitaria necesita una declaración de conmutación para todas las implementaciones, que no es lo que quiero. – dlras2

+0

No necesita usar una declaración de interruptor si no desea. Mencioné que también puedes usar la reflexión. Vea la respuesta de Anthony para un ejemplo de esto. También actualicé mi publicación para incluir un ejemplo de uso de la reflexión para instanciar las diversas implementaciones concretas de la interfaz. –

+0

Probablemente escribiría esto para tomar instancias de 'IMyInterface' en lugar de solo el nombre, pero aún +1. – dlras2

2

Expandiendo en Joe's respuesta, puede usar el atributo [TestCaseSource] en NUnit de una manera similar a RowTest de MBUnit. Puede crear una fuente de caso de prueba con sus nombres de clase allí. A continuación, puede decorar cada prueba que el atributo TestCaseSource. Luego, usando Activator.CreateInstance, puedes enviar contenido a la interfaz y estarás configurado.

Algo como esto (nota - la cabeza compilado)

string[] MyClassNameList = { "Class1", "Class2" }; 

[TestCaseSource(MyClassNameList)] 
public void Test1(string className) 
{ 
    var instance = Activator.CreateInstance(Type.FromName(className)) as IMyInterface; 

    ... 
} 
1

Ésta es mi aplicación concreta basa fuera de avandeursen's answer:

[TestClass] 
public abstract class IMyInterfaceTests 
{ 
    protected abstract IMyInterface CreateInstance(); 

    [TestMethod] 
    public void SomeTest() 
    { 
     IMyInterface instance = CreateInstance(); 
     // Run the test 
    } 
} 

Cada implementación de la interfaz define entonces la siguiente clase de prueba:

[TestClass] 
public class MyImplementationTests : IMyInterfaceTests 
{ 
    protected override IMyInterface CreateInstance() 
    { 
     return new MyImplementation(); 
    } 
} 

SomeTest se ejecuta una vez para cada hormigón TestClass derivado de IMyInterfaceTests. Al utilizar una clase base abstracta, evito la necesidad de implementaciones simuladas. Asegúrese de agregar TestClassAttribute a ambas clases o esto no funcionará. Por último, puede agregar cualquier prueba específica de implementación (como constructores) a la clase secundaria, si lo desea.

+0

¿está utilizando un marco de prueba en particular? Estoy usando las pruebas unitarias incluidas en VS2012, y las pruebas heredadas de otro proyecto de prueba "compartido" (espacio de nombres) no se recogen. – drzaus

+0

raro: pruebas heredadas dentro del mismo proyecto, diferentes espacios de nombres aparecen bien. solo cuando están en un proyecto diferente, no se están registrando. ¿Qué me estoy perdiendo? – drzaus

Cuestiones relacionadas