una que puede programacion interfaz interfaces instancia implementacion declaracion declara cómo crear caracteristicas c# .net unit-testing nunit

c# - puede - NUnit: cómo probar todas las clases que implementan una interfaz particular



no se puede crear una instancia de una interfaz (7)

Si tengo la interfaz IFoo, y tengo varias clases que la implementan, ¿cuál es la mejor / más elegante / manera inteligente de probar todas esas clases contra la interfaz?

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

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


No creo que esta sea la mejor práctica.

La simple verdad es que una interfaz no es más que un contrato que implementa un método. No es 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 por las cuales espigar 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 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, implementando 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 se refiera adecuadamente a ese método de extensión emitirá precisamente ese método de extensión, por lo que solo debe probarlo una vez.


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.


@Emperor 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ía tener un accesorio de prueba independiente 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 muy bueno). En realidad esto está bien, tal vez incluso sea ventajoso porque agrupa tus 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.


¿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 para niños.


Si tiene clases para implementar cualquier interfaz, entonces 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 la duplicación de código y código de prueba, es posible que desee crear una clase abstracta que maneje el código recurrente .

Por ejemplo, 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(); }

Probando eso es fácil; implementar la clase abstracta en la clase de prueba 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 deje que la clase de prueba amplíe la clase abstracta en sí misma 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 ocupe 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 método CommonCode y SpecificCode se llama stub o hook. La idea es que cualquier persona pueda extender el comportamiento sin la necesidad de conocer las cosas detrás de escena.

Muchos frameworks confían en este patrón de comportamiento, por ejemplo ASP.NET donde tienes que implementar los hooks en una página o controles de usuario como el método Page_Load generado que es llamado por el evento Load , el método de la plantilla llama a los hooks detrás del 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.


Al probar una interfaz o contrato de clase base, prefiero dejar que el marco de prueba se ocupe automáticamente 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 , creé una biblioteca Type Resolver para buscar todas las implementaciones de un tipo en particular (las extensiones de xUnit.net son solo un envoltorio delgado sobre la funcionalidad de Type Resolver, por lo que se puede adaptar para su uso en otros frameworks).
  • En MbUnit , puede usar un CombinatorialTest con los atributos de UsingImplementations en los parámetros.
  • Para otros marcos, el patrón de clase base que Spoike mencionó 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.


No estoy de acuerdo con Jon Limjap cuando dice:

No es 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 por las cuales espigar 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 agnóstico del lenguaje:

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 de su unidad en LinkedList, ArrayList, CircularlyLinkedList y todas las demás deben probar no solo que las listas se devuelven, sino también que se han modificado correctamente.

Hubo una pregunta anterior sobre el diseño por contrato, que puede ayudarlo a orientarlo en la dirección correcta de una manera de SECAR estas pruebas.

Si no quiere los gastos generales de los contratos, recomiendo los equipos de prueba, en la línea de lo que Spoike recomendó:

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