Pregunta

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 con la interfaz?

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

¿Qué consideraría usted mejor práctica?Estoy usando NUnit, pero supongo que los ejemplos de cualquier marco de prueba unitario serían válidos.

¿Fue útil?

Solución

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

En su lugar, optemos por una ruta más inteligente;si tu objetivo es Evite la duplicación de código y código de prueba. es posible que desees crear una clase abstracta que maneje el periódico código.

P.ej.tienes 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();

}

Probar eso es fácil;implementar la clase abstracta en la clase de prueba ya sea como una clase interna:

[TestFixture]
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 deje que la clase de prueba extienda la clase abstracta si eso le conviene.

[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 encargue del código común que implica una interfaz proporciona un diseño de código mucho más limpio.

Espero que esto tenga sentido para ti.


Como nota al margen, este es un patrón de diseño común llamado Patrón de método de plantilla.En el ejemplo anterior, el método de plantilla es el CommonCode método y SpecificCode se llama trozo o gancho.La idea es que cualquiera pueda ampliar el comportamiento sin la necesidad de conocer lo que hay detrás de escena.

Muchos marcos se basan en este patrón de comportamiento, p. ASP.NET donde tienes que implementar los ganchos en una página o controles de usuario como el generado Page_Load método que es llamado por el Load evento, el método de plantilla llama a los ganchos detrás de escena.Hay muchos más ejemplos de esto.Básicamente, cualquier cosa que tenga que implementar que utilice las palabras "cargar", "init" o "renderizar" se llama mediante un método de plantilla.

Otros consejos

no estoy de acuerdo con Jon Limjap cuando él dice,

No es un contrato sobre a.) cómo se debe implementar el método y b.) qué debería hacer exactamente ese método (solo garantiza el tipo de devolución), las dos razones que deduzco serían su motivo para querer este tipo de prueba.

Podría 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);

}

Sus pruebas unitarias en LinkedList, ArrayList, CircularlyLinkedList y todas las demás deben probar no solo que se devuelvan las listas en sí, sino también que se hayan modificado correctamente.

Había un pregunta anterior en diseño por contrato, que puede ayudarlo a orientarlo en la dirección correcta sobre una forma de secar estas pruebas.

Si no desea los gastos generales de los contratos, le recomiendo realizar pruebas, en la línea de lo que espolón recomendado:

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
  }
}

No creo que esta sea la mejor práctica.

La simple verdad es que una interfaz no es más que un contrato por el que se implementa un método.Es no un contrato sobre a.) cómo se debe implementar el método y b.) qué debería hacer exactamente ese método (solo garantiza el tipo de devolución), las dos razones que deduzco serían su motivo para querer este tipo de prueba.

Si realmente desea tener el control de la implementación de su método, tiene la opción de:

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

Ejemplo:

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

Cualquier implementación que haga referencia adecuada a ese método de extensión emitirá precisamente ese método de extensión, por lo que solo necesitará probarlo una vez.

@Emperador XLII

Me gusta el sonido de las pruebas combinatorias en MbUnit, probé la técnica de prueba de interfaz de clase base abstracta con NUnit y, aunque funciona, necesitarías tener un dispositivo de prueba separado para cada interfaz que implemente 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 pudiera ser más inteligente.Si pudiera usar un atributo para marcar una clase como una clase de prueba 'oficial' para una interfaz, el marco buscaría en el ensamblado bajo prueba todas las clases que implementan la interfaz y ejecutaría esas pruebas en ella.

Eso sería genial.

¿Qué tal una jerarquía de clases de [TestFixture]?Coloque el código de prueba común en la clase de prueba base y heredéelo en las clases de prueba secundarias.

Al probar una interfaz o un contrato de clase base, prefiero dejar que el marco de prueba se encargue automáticamente de encontrar todos los implementadores.Esto le permite concentrarse en la interfaz que se está probando y estar razonablemente seguro de que se probarán todas las implementaciones, sin tener que realizar muchas implementaciones manuales.

  • Para xUnit.net, creé un Tipo de resolución biblioteca para buscar todas las implementaciones de un tipo particular (las extensiones xUnit.net son solo una envoltura delgada sobre la funcionalidad Type Resolver, por lo que se puede adaptar para su uso en otros marcos).
  • En Unidad Mb, puedes usar un CombinatorialTest con UsingImplementations atributos en los parámetros.
  • Para otros marcos, el patrón de clase base espolón mencionado puede ser útil.

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

No uso NUnit pero he probado interfaces C++.Primero probaría una clase TestFoo que es una implementación básica para asegurarme de que el material genérico funcione.Luego sólo necesitas probar las cosas que son exclusivas de cada interfaz.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top