2008-09-02 12 views
28

Si tengo la interfaz IFoo y tengo varias clases que la implementan, ¿cuál es la mejor/más elegante/más inteligente forma de probar todas esas clases contra la interfaz?NUnit - Cómo probar todas las clases que implementan una interfaz particular

Me gustaría reducir la duplicación del código de prueba, pero aún "seguir siendo fiel" a los principios de las pruebas unitarias.

¿Cuál consideraría la mejor práctica? Estoy usando NUnit, pero supongo que los ejemplos de cualquier marco de prueba de la Unidad serían válidos

Respuesta

13

Si tiene clases que implementan una interfaz, todas necesitan implementar los métodos en esa interfaz. Para probar estas clases, necesitas crear una clase de prueba unitaria para cada una de las clases.

Vamos a ir con una ruta más inteligente en su lugar; si su objetivo es evitar el código y la duplicación del código de prueba es posible que desee crear una clase abstracta que maneje el código recurrente.

E.g. usted tiene la siguiente interfaz:

public interface IFoo { 

    public void CommonCode(); 

    public void SpecificCode(); 

} 

Es posible que desee crear una clase abstracta:

public abstract class AbstractFoo : IFoo { 

    public void CommonCode() { 
      SpecificCode(); 
    } 

    public abstract void SpecificCode(); 

} 

Prueba de que es fácil; implementar la clase abstracta en la clase de prueba, ya sea como una clase interna:

[TextFixture] 
public void TestClass { 

    private class TestFoo : AbstractFoo { 
     boolean hasCalledSpecificCode = false; 
     public void SpecificCode() { 
      hasCalledSpecificCode = true; 
     } 
    } 

    [Test] 
    public void testCommonCallsSpecificCode() { 
     TestFoo fooFighter = new TestFoo(); 
     fooFighter.CommonCode(); 
     Assert.That(fooFighter.hasCalledSpecificCode, Is.True()); 
    } 
} 

... o dejar que la clase de prueba extender la clase abstracta en sí, si que se ajuste a su fantasía.

[TestFixture] 
public void TestClass : AbstractFoo { 

    boolean hasCalledSpecificCode; 
    public void specificCode() { 
     hasCalledSpecificCode = true; 
    } 

    [Test] 
    public void testCommonCallsSpecificCode() { 
     AbstractFoo fooFighter = this; 
     hasCalledSpecificCode = false; 
     fooFighter.CommonCode(); 
     Assert.That(fooFighter.hasCalledSpecificCode, Is.True()); 
    }   

} 

Tener una clase abstracta que se ocupe del código común que una interfaz implica da un diseño de código mucho más limpio.

Espero que esto tenga sentido para usted.


Como nota al margen, este es un patrón de diseño común que se llama la Template Method pattern. En el ejemplo anterior, el método de la plantilla es el método CommonCode y SpecificCode se llama un stub o un gancho. La idea es que cualquier persona pueda extender el comportamiento sin la necesidad de conocer las cosas detrás de escena.

Muchos frameworks se basan en este patrón de comportamiento, p. ASP.NET donde tiene que implementar los ganchos en una página o en los controles de un usuario como el método Page_Load generado que se llama por el evento Load, el método de la plantilla llama a los ganchos detrás de las escenas. Hay muchos más ejemplos de esto. Básicamente, todo lo que tiene que implementar que utiliza las palabras "cargar", "init" o "renderizar" se llama mediante un método de plantilla.

+1

@Spoike: +1, pero un pequeño inconveniente es que su código no es C#. – user7116

+1

@sixlettervariables: sí, me olvidé de agregar palabra clave virtual y otras cosas. Yo culpo a toda la programación de Java que tuve que hacer al mismo tiempo que escribí esta entrada;) – Spoike

+0

¡Buena respuesta! Actualmente, NUnit admite clases de prueba genéricas y el atributo TestFixture se puede usar para proporcionar los tipos específicos que se utilizarán cuando se ejecute la prueba. Escribí una [publicación de blog] (http://softwareonastring.com/2015/03/22/testing-every-implementer-of-an-interface-with-the-same-tests-using-nunit) sobre cómo probar cada implementador de una interfaz que muestra estas características. –

0

No uso NUnit pero he probado interfaces C++. Primero probaría una clase TestFoo, que es una implementación básica de la misma para asegurarse de que las cosas genéricas funcionen. Entonces solo necesita probar las cosas que son únicas para cada interfaz.

3

No creo que esta sea la mejor práctica.

La verdad es que una interfaz no es más que un contrato que implementa un método.Es no un contrato ya sea en a) cómo debe implementarse el método yb) qué método debería estar haciendo exactamente (solo garantiza el tipo de devolución), las dos razones que veo son su motivo para querer este tipo de prueba.

Si realmente quiere estar en control de su implementación del método, usted tiene la opción de:

  • Su aplicación como un método en una clase abstracta, y heredan de eso. Aún tendrá que heredarlo en una clase concreta, pero está seguro de que, a menos que se invalide explícitamente, ese método hará lo correcto.
  • En .NET 3.5/C# 3.0, que implementa el método como un método de extensión referencia a la interfaz de

Ejemplo:

public static ReturnType MethodName (this IMyinterface myImplementation, SomeObject someParameter) 
{ 
    //method body goes here 
} 

Cualquier implementación referencia correctamente a ese método de extensión emitirá precisamente que la extensión método por lo que solo debe probarlo una vez.

11

estoy de acuerdo con Jon Limjap cuando dice,

No es un contrato a cada una.) Cómo el método que debe aplicarse y b.) Lo que el método debería estar haciendo exactamente (sólo garantiza la tipo de devolución), las dos razones que veo son tu motivo para querer este tipo de prueba.

Puede haber muchas partes del contrato no especificadas en el tipo de devolución. Un ejemplo independiente del idioma:

public interface List { 

    // adds o and returns the list 
    public List add(Object o); 

    // removed the first occurrence of o and returns the list 
    public List remove(Object o); 

} 

las pruebas unitarias en LinkedList, ArrayList, CircularlyLinkedList, y todos los demás deben probar no sólo que las listas son devueltos a sí mismos, sino que también se han modificado correctamente.

Hubo un earlier question en el diseño por contrato, que puede ayudarlo a orientarlo en la dirección correcta en una forma de DRYing up estas pruebas.

Si no desea que los gastos generales de los contratos, lo recomiendo bancos de pruebas, a lo largo de las líneas de lo Spoike recomendados:

abstract class BaseListTest { 

    abstract public List newListInstance(); 

    public void testAddToList() { 
    // do some adding tests 
    } 

    public void testRemoveFromList() { 
    // do some removing tests 
    } 

} 

class ArrayListTest < BaseListTest { 
    List newListInstance() { new ArrayList(); } 

    public void arrayListSpecificTest1() { 
    // test something about ArrayLists beyond the List requirements 
    } 
} 
1

Cuando se prueba un contrato de interfaz o base de clase, prefiero dejar que la prueba marco automáticamente se ocupa de encontrar todos los implementadores. Esto le permite concentrarse en la interfaz bajo prueba y estar razonablemente seguro de que todas las implementaciones serán probadas, sin tener que realizar mucha implementación manual.

  • Para xUnit.net, he creado una biblioteca Type Resolver para buscar todas las implementaciones de un tipo determinado (las extensiones xUnit.NET son sólo una envoltura delgada sobre la funcionalidad Tipo de resolución, por lo que se pueden adaptar para su uso en otros marcos)
  • En MbUnit, puede usar un CombinatorialTest con atributos UsingImplementations en los parámetros.
  • Para otros marcos, el patrón de clase base Spoike mencionado puede ser útil.

Más allá de probar los conceptos básicos de la interfaz, también debe probar que cada implementación individual sigue sus requisitos particulares.

1

@Emperor XLII

me gusta el sonido de las pruebas combinatorias en MbUnit, he probado la técnica de prueba de interfaz de la clase base abstracta con NUnit, y aunque funciona, que había necesidad de tener una prueba separada accesorio para cada interfaz que implementa una clase (ya que en C# no hay herencia múltiple, aunque se pueden usar clases internas, lo cual es bastante bueno). En realidad esto está bien, tal vez incluso sea ventajoso porque agrupa las pruebas para la clase de implementación por interfaz. Pero sería bueno si el marco podría ser más inteligente. Si pudiera usar un atributo para marcar una clase como una clase de prueba "oficial" para una interfaz, y el marco buscaría en el ensamblaje bajo prueba para todas las clases que implementan la interfaz, y ejecutar esas pruebas en él.

Eso sería genial.

1

¿Qué tal una jerarquía de clases de [TestFixture]? Coloque el código de prueba común en la clase de prueba base y hágalo heredar en clases de prueba.

Cuestiones relacionadas