2008-08-09 20 views
55

Mocking sealed classes puede ser bastante doloroso. Actualmente estoy a favor de un Adapter pattern para manejar esto, pero algo sobre simplemente se siente raro.¿Cómo te burlas de una clase Sellada?

Entonces, ¿cuál es la mejor forma de burlarse de las clases selladas?

Las respuestas de Java son más que bienvenidas. De hecho, me gustaría anticipar que la comunidad Java ha estado lidiando con esto por más tiempo y tiene mucho que ofrecer.

Pero aquí están algunas de las opiniones de .NET:

+0

¡No se burle! [Usar composición en su lugar] (http://stackoverflow.com/a/891820/274502). – cregox

Respuesta

6

Mi regla general es que los objetos que necesito para burlarse deben tener una interfaz común también. Creo que esto es correcto en cuanto al diseño y hace que las pruebas sean mucho más fáciles (y generalmente es lo que obtienes si haces TDD). Se puede leer más acerca de esto en el Blog de Pruebas de Google latest post (Ver el punto 9).

Además, he estado trabajando principalmente en Java en los últimos 4 años y puedo decir que puedo contar con una mano la cantidad de veces que he creado una clase final (sellada). Otra regla aquí es que siempre debería tener una buena razón para sellar una clase, en lugar de sellarla por defecto.

+19

Yo diría que debe tener una buena razón * * para sellar una clase. Dejar una clase abierta significa que necesita pensar cómo los heredarán, lo que abre una gran cantidad de decisiones sobre todo el código de la clase (virtualidad, propiedades protegidas o variables de miembros privados, etc.) Es * difícil * diseñar correctamente una clase para la herencia. No deberías poder extender una clase solo por la extensión; la derivación debe significar algo específico para el problema que se modela. De lo contrario, favorece la composición en su lugar. –

+2

Bryan - Entonces, ¿cómo se burlaría de una clase sellada? Estoy de acuerdo con usted desde un punto de vista teórico, pero si escribe algo que depende de su clase sellada, entonces sus pruebas también dependen de la clase sellada. –

+4

@abyx: Desafortunadamente, no siempre es la elección del desarrollador si una clase está sellada o no. Tome, por ejemplo, System.Web.HttpServerUtility en ASP.NET ... – chiccodoro

1

¿Hay una manera de implementar una clase sellada desde una interfaz ... y burlarse de la interfaz en su lugar?

Algo en mí se siente que el tener clases cerradas que está mal en el primer lugar, pero eso es sólo conmigo :)

+2

Creo que las clases selladas son geniales ... el problema es la prueba. Apesta que tengamos que cambiar todo nuestro diseño debido a las limitaciones para probar proyectos .NET. En muchos casos D.I. es simplemente una forma de evitar el hecho de que las clases/métodos estáticos son difíciles de probar. –

18

Para .NET, podría usar algo como TypeMock, que usa la API de creación de perfiles y le permite enganchar llamadas a casi cualquier cosa.

+7

+1. Usa las herramientas correctas. No permita que las herramientas le dicten cómo debe hacer las cosas. Las API son para personas, no para herramientas; diseñelas como tales. Use DI e interfaces y desacople completo donde tenga sentido, no solo porque lo necesite para probar herramientas. –

+2

Pavel: el problema es que la forma "correcta" en .NET generalmente te lleva por un camino de 'Usar TypeMock para probar'. Seguir un camino donde solo hay una herramienta de prueba disponible tampoco parece ser muy bueno. –

+5

Algunas personas pueden no desear pagar $ 80 al mes por un marco de prueba. –

4

El problema con TypeMock es que justifica un mal diseño. Ahora, sé que a menudo es el diseño incorrecto de alguien más que se esconde, pero permitirlo en su proceso de desarrollo puede llevar muy fácilmente a sus propios malos diseños.

Creo que si vas a utilizar un marco de burla, deberías usar uno tradicional (como Moq) y crear una capa de aislamiento alrededor de lo que no puede ser mofado, y burlar la capa de aislamiento en su lugar.

+11

No abofetear las interfaces en todo lo que se ve solo porque las necesita debido a las deficiencias de sus herramientas de prueba no es un mal diseño. De hecho, todo lo contrario - es _sane_ design el diseño, no sobre la conformidad con sus herramientas. Lo juro, a veces pienso que TDD, ya que a menudo se practica dogmáticamente, debería llamarse realmente "diseño impulsado por herramientas". También vea http://weblogs.asp.net/rosherove/archive/2008/01/17/is-typemock-too-powerful-will-it-kill-design-for-testability.aspx –

+0

Pavel: ¿tiene otra herramienta que no sea TypeMock que pueda proporcionar este tipo de prueba? Las pruebas de clases construidas con diseños sanos (por ejemplo, usando métodos estáticos, nuevas llamadas en código, limitando las interfaces donde se necesitan múltiples implementaciones, etc.) a menudo son casi imposibles de probar. –

+1

Estoy de acuerdo con Brad aquí. Crear un objeto simulado implica que está probando el comportamiento público de ese tipo. Es decir, usted está diciendo específicamente: "Necesito que la API pública de este objeto se comporte de cierta manera". Este comportamiento es * independiente * de sin embargo dicha API está implementada. Esto significa que tiene una abstracción ya definida (aunque implícita) que necesita comportarse de cierta manera. En un sistema de tipo estático, esto debe hacerse explícito creando un tipo abstracto. –

1

Generalmente tomo la ruta de crear una interfaz y una clase de adaptador/proxy para facilitar la burla del tipo sellado. Sin embargo, también experimenté omitiendo la creación de la interfaz y haciendo que el proxy no sea sellado con métodos virtuales. Esto funcionó bien cuando el proxy es realmente una clase base natural que encapsula y los usuarios forman parte de la clase sellada.

Al tratar con el código que requería esta adaptación, me cansé de realizar las mismas acciones para crear la interfaz y el tipo de proxy, así que implementé una biblioteca para automatizar la tarea.

El código es algo más sofisticado que el ejemplo del artículo al que hace referencia, ya que produce un ensamblaje (en lugar del código fuente), permite la generación de código en cualquier tipo y no requiere tanto configuración.

Para obtener más información, consulte this page.

4

Casi siempre evito tener dependencias de clases externas en lo profundo de mi código. En cambio, prefiero usar un adaptador/puente para hablar con ellos. De esa manera, estoy tratando con mi semántica, y el dolor de traducir está aislado en una clase.

También hace que sea más fácil cambiar mis dependencias a largo plazo.

1

Es perfectamente razonable burlarse de una clase sellada porque muchas clases de marco están selladas.

En mi caso estoy tratando de burlarme de la clase MessageQueue de .NET para que pueda TDD mi elegante lógica de manejo de excepciones.

Si alguien tiene ideas sobre cómo superar el error de Moq con respecto a "Configuración no válida en un miembro no invalidable", hágamelo saber.

código:

[TestMethod] 
    public void Test() 
    { 
     Queue<Message> messages = new Queue<Message>(); 
     Action<Message> sendDelegate = msg => messages.Enqueue(msg); 
     Func<TimeSpan, MessageQueueTransaction, Message> receiveDelegate = 
      (v1, v2) => 
      { 
       throw new Exception("Test Exception to simulate a failed queue read."); 
      }; 

     MessageQueue mockQueue = QueueMonitorHelper.MockQueue(sendDelegate, receiveDelegate).Object; 
    } 
    public static Mock<MessageQueue> MockQueue 
       (Action<Message> sendDelegate, Func<TimeSpan, MessageQueueTransaction, Message> receiveDelegate) 
    { 
     Mock<MessageQueue> mockQueue = new Mock<MessageQueue>(MockBehavior.Strict); 

     Expression<Action<MessageQueue>> sendMock = (msmq) => msmq.Send(It.IsAny<Message>()); //message => messages.Enqueue(message); 
     mockQueue.Setup(sendMock).Callback<Message>(sendDelegate); 

     Expression<Func<MessageQueue, Message>> receiveMock = (msmq) => msmq.Receive(It.IsAny<TimeSpan>(), It.IsAny<MessageQueueTransaction>()); 
     mockQueue.Setup(receiveMock).Returns<TimeSpan, MessageQueueTransaction>(receiveDelegate); 

     return mockQueue; 
    } 
+0

He encontrado una solución y publicado aquí: http://www.dotnetmonkey.net/post/Hard-to-Moq.aspx –

13

Creo que Moles, de Microsoft Research, le permite hacer eso. Desde la página de Moles:

Moles se puede utilizar para desviar cualquier .NET método, incluyendo no virtuales/ métodos estáticos en los tipos de sellados.

ACTUALIZACIÓN: hay un nuevo marco denominado "falsificaciones" en la próxima versión VS 11 que está diseñado para reemplazar Moles:

El Fakes Framework in Visual Studio 11 es la próxima generación de Moles & trozos, y eventualmente lo reemplazará. Sin embargo, las falsificaciones son diferentes a las de Moles, por lo tanto, pasar de Moles a Fakes requerirá algunas modificaciones a su código. Una guía para esta migración estará disponible en una fecha posterior.

Requisitos: Visual Studio 11 Ultimate, .NET 4,5

1

Aunque su momento sólo está disponible en versión beta, creo que es el mantenimiento de la pena en cuenta la característica de la nueva shimFakes framework (parte de la Visual Studio 11 Versión Beta).

Los tipos de cuña proporcionan un mecanismo para desviar cualquier método .NET a un delegado definido por el usuario. Los tipos de corrección son generados por código por el generador de Fakes, y utilizan delegados, que llamamos tipos de corrección, para especificar las nuevas implementaciones de métodos. Debajo del capó, los tipos de corrección utilizan devoluciones de llamada que se inyectaron en tiempo de ejecución en el método cuerpos MSIL.

Personalmente estaba buscando usar esto para burlarme de los métodos en las clases de framework selladas como DrawingContext.

2

Me encontré con este problema recientemente y después de leer/buscar en la web, parece que no hay una manera fácil de usar, excepto para utilizar otra herramienta como se mencionó anteriormente. O crudo de manejar cosas como yo lo hice:

  • Crear instancia de clase sellada sin tener que llamar al constructor.
  • System.Runtime.Serialization.FormatterServices.GetUninitializedObject (instanceType);

  • Asignar valores a sus propiedades/campos a través de la reflexión

  • YourObject.GetType() GetProperty ("PropertyName") FijarValor (dto, nuevoValor, null)..;
  • YourObject.GetType(). GetField ("FieldName"). SetValue (dto, newValue);
+0

Esta es la única solución que tiene sentido y funciona para burlarse de las cosas internas de Microsoft sin agregar ninguna biblioteca adicional como falsificaciones, ¡gracias! – Ben

Cuestiones relacionadas