¿Cómo me burlo de una clase sin una interfaz?

81

Estoy trabajando en .NET 4.0 usando C # en Windows 7.

Quiero probar la comunicación entre algunos métodos usando mock. El único problema es que quiero hacerlo sin implementar una interfaz. ¿Es eso posible?

Acabo de leer muchos temas y algunos tutoriales sobre objetos simulados, pero todos se utilizan para simular interfaces, no las clases. Intenté utilizar los marcos Rhino y Moq.

Vinicius Seganfredo
fuente
1
Realmente muerde que estas herramientas se crean desde la perspectiva de utilizar "IInterfaces" exclusivamente.
AR
4
Se crean asumiendo que está utilizando DI basada en interfaz. Este es un patrón bastante estándar en estos días.
Maess
Desafortunadamente, ese patrón entra en conflicto con el "patrón" de Tipo inmutable :(
Matthew Watson
3
Hay varios métodos que no se mencionan aquí en esta respuesta
BlueRaja - Danny Pflughoeft

Respuestas:

65

Simplemente marque cualquier método que necesite falsificar como virtual(y no privado). Entonces podrás crear una falsificación que pueda anular el método.

Si usa new Mock<Type>y no tiene un constructor sin parámetros, puede pasar los parámetros como los argumentos de la llamada anterior, ya que toma un tipo deparam Objects

Justin Pihony
fuente
1
Esto me deja preguntándome si es necesario crear una interfaz para cada clase de la que quiero burlarme. ¿No podríamos usar clases concretas para burlarse si no tienen una interfaz?
orad
13
@orad De hecho, tiendo a crear una clase primero, solo creando una interfaz si necesito romper una funcionalidad común.
Justin Pihony
¿Cómo se pasa la simulación a un objeto que espera el tipo especificado? Si creo un simulacro de 'new Mock <MyType>' e intento pasarlo a un objeto que espera MyType, aparece el error "No se puede convertir Mock <MyType> en MyType".
Neutrino
2
Lo resolví, si define su simulacro como 'var myMock = new Mock <MyType> ()' debe pasarlo a la clase que usa el simulacro como myMock.Object.
Neutrino
4
El problema principal surge cuando no hay un constructor sin parámetros y el constructor parametrizado en la clase concreta cerrada no es público. (Aquí, por 'cerrado' quiero decir, en un paquete externo). Entonces, uno no puede implementar una interfaz, no puede hacer que ese método sea virtual y tampoco puede usar el constructor parametrizado.
Abhilash Pokhriyal
22

La mayoría de los marcos de simulación (incluidos Moq y RhinoMocks) generan clases de proxy como sustituto de su clase simulada y anulan los métodos virtuales con el comportamiento que usted define. Debido a esto, solo puede simular interfaces o métodos virtuales en clases concretas o abstractas. Además, si se está burlando de una clase concreta, casi siempre debe proporcionar un constructor sin parámetros para que el marco burlón sepa cómo crear una instancia de la clase.

¿Por qué la aversión a crear interfaces en su código?

mate
fuente
102
¿Porque obstruye el código base con toneladas de interfaces que, si no fuera por alguna limitación técnica de los marcos de prueba, serían completamente innecesarias?
BlueRaja - Danny Pflughoeft
9
Ha escuchado la expresión "el alcance excede el alcance". Eso explica por qué los desarrolladores siempre se quejan. El lenguaje C # no se diseñó teniendo en cuenta las pruebas unitarias. Por eso es realmente difícil inyectar dependencias simuladas sin un enorme desorden de interfaces o métodos virtuales inútiles. Quizás alguien invente pronto un lenguaje que sea fácil de probar unitariamente. Para entonces tendremos algo más de qué quejarnos.
John Henckel
13
Me alegra saber que no soy el único que ve las interfaces y los métodos virtuales como un desorden si su único propósito es servir pruebas.
aaaaaa
13
"El único propósito es servir pruebas", pero ¿no es importante para usted crear código comprobable?
MakkyNZ
12
"¿Por qué la aversión a crear interfaces en su código?" Porque no escribí este objeto y no tengo acceso al código. Necesito crear una simulación de un objeto cerrado que no poseo y que no implementa una interfaz.
Sean Worle
16

Con MoQ, puede simular clases concretas:

var mocked = new Mock<MyConcreteClass>();

pero esto le permite anular el virtualcódigo (métodos y propiedades).

Roy Dictus
fuente
1
Lo intenté, pero cuando ejecuto mi proyecto de prueba, mi programa arroja una excepción: "No se puede crear una instancia del proxy de la clase" "No se pudo encontrar un constructor sin parámetros".
Vinicius Seganfredo
2
Simplemente pase los parámetros del constructor al constructor Mock <>. Por ejemplonew Mock<MyConcreteClass>(param1, anotherParam, thirdParam, evenMoreParams);
Bozhidar Stoyneff
1
¿Por qué no crear una interfaz para la clase?
Robert Perry
11

Creo que es mejor crear una interfaz para esa clase. Y cree una prueba unitaria usando la interfaz.

Si no tiene acceso a esa clase, puede crear un adaptador para esa clase.

Por ejemplo:

public class RealClass
{
    int DoSomething(string input)
    {
        // real implementation here
    }
}

public interface IRealClassAdapter
{
    int DoSomething(string input);
}

public class RealClassAdapter : IRealClassAdapter
{
    readonly RealClass _realClass;

    public RealClassAdapter() => _realClass = new RealClass();

    int DoSomething(string input) => _realClass.DoSomething(input);
}

De esta manera, puede crear fácilmente un simulacro para su clase usando IRealClassAdapter.

Espero que funcione.

Anang Satria
fuente
4
Esto es terrible para el mantenimiento. Si desea agregar un nuevo método a RealClass, también debe agregarlo a IReadClassAdapter y también a RealClassAdapter. ¡TRIPLE esfuerzo! Una mejor solución es simplemente agregar la palabra clave "virtual" a cada método público en RealClass.
John Henckel
12
@JohnHenckel Supongo que no tenemos acceso a RealClass. Entonces, agregar el método "virtual" no es una opción en mi opinión. Si tiene acceso a la clase real, es mejor implementar la interfaz directamente en la nueva clase, creo que esa es la mejor práctica.
Anang Satria
Gracias. Esta parece ser la única solución legítima para burlarse de las clases sin acceso. Incluso es un montón de código "estúpido", lo necesito para ejecutar el código en un "entorno inapropiado"
Michael Spannbauer
7

Los marcos de simulación estándar están creando clases de proxy. Esta es la razón por la que técnicamente se limitan a interfaces y métodos virtuales.

Si también desea simular métodos 'normales', necesita una herramienta que funcione con instrumentación en lugar de generación de proxy. Por ejemplo, MS Moles y Typemock pueden hacer eso. Pero el primero tiene una 'API' horrible, y el segundo es comercial.

Thomas Weller
fuente
¿Podemos burlarnos de la clase normal con ese método público?
SivaRajini
2

Si lo peor empeora, puede crear una interfaz y un par de adaptadores. Cambiaría todos los usos de ConcreteClass para usar la interfaz en su lugar, y siempre pasaría el adaptador en lugar de la clase concreta en el código de producción.

El adaptador implementa la interfaz, por lo que el simulacro también puede implementar la interfaz.

Es más un andamiaje que simplemente hacer un método virtual o simplemente agregar una interfaz, pero si no tiene acceso a la fuente de la clase concreta, puede salir de un aprieto.

Tim Ottinger
fuente
1

Me enfrenté a algo así en uno de los proyectos antiguos y heredados en los que trabajé que no contiene interfaces o mejores prácticas y también es demasiado difícil hacerlos cumplir, construir cosas nuevamente o refactorizar el código debido a la madurez del negocio del proyecto, así que en mi proyecto UnitTest, solía crear un Wrapper sobre las clases que quiero simular y esa interfaz de implementación de envoltorio que contiene todos mis métodos necesarios con los que quiero configurar y trabajar.Ahora puedo simular el envoltorio en lugar de la clase real.

Por ejemplo:

Servicio que desea probar que no contiene métodos virtuales o interfaz de implementación

public class ServiceA{

public void A(){}

public String B(){}

}

Envoltorio a moq

public class ServiceAWrapper : IServiceAWrapper{

public void A(){}

public String B(){}

}

La interfaz Wrapper

public interface IServiceAWrapper{

void A();

String B();

}

En la prueba unitaria, ahora puede simular la envoltura:

    public void A_Run_ChangeStateOfX()
    {
    var moq = new Mock<IServiceAWrapper>();
    moq.Setup(...);
    }

Puede que esta no sea la mejor práctica, pero si las reglas de su proyecto lo obligan a hacerlo, hágalo. También coloque todos sus Wrappers dentro de su proyecto de prueba unitaria o proyecto Helper especificado solo para las pruebas unitarias para no sobrecargar el proyecto con envoltorios o adaptadores innecesarios.

Actualización: esta respuesta de más de un año, pero en este año enfrenté muchos escenarios similares con diferentes soluciones. Por ejemplo, es muy fácil usar Microsoft Fake Framework para crear simulacros, falsificaciones y stubs e incluso probar métodos privados y protegidos sin interfaces. Puede leer: https://docs.microsoft.com/en-us/visualstudio/test/isolates-code-under-test-with-microsoft-fakes?view=vs-2017

Marzouk
fuente