2009-10-01 17 views
14

Actualmente estoy involucrado en el desarrollo con C# - Aquí hay algunos antecedentes: Implementamos MVP con nuestra aplicación cliente y tenemos una regla ciclomática que establece que ningún método debe tener una complejidad ciclomática mayor que 5. Esto lleva a muchos pequeños métodos privados que generalmente son responsables de una cosa.Unidad de prueba de código privado

Mi pregunta es sobre la unidad de prueba de una clase:

Prueba de la aplicación privada a través de los métodos públicos es todo muy bien ... No tengo un problema de la aplicación de este.

Pero ... ¿qué pasa con los siguientes casos:

Ejemplo 1. gestionar el resultado de una solicitud de retrival datos asíncrono (El método de devolución de llamada no debe ser puramente pública para la prueba)

ejemplo 2. un controlador de eventos que realiza una operación (por ejemplo, actualizar el texto de una vista de etiqueta - ejemplo tonto lo sé ...)

ejemplo 3. Está utilizando un marco de terceros que le permite ampliar anulando métodos virtuales protegidos (la ruta de acceso de los métodos públicos a estos métodos virtuales generalmente se trata como programación de caja negra y tendrá todo tipo de dependencias que el marco proporciona 't quiero saber sobre)

Los ejemplos de arriba no me parecen ser el resultado de un diseño deficiente. Tampoco parecen ser candidatos para pasar a una clase separada para realizar las pruebas de forma aislada ya que dichos métodos perderán su contexto.

¿Alguien tiene alguna idea al respecto?

Saludos, Jason

EDIT: no creo que era lo suficientemente claro en mi pregunta original - Puedo probar métodos privados que utilizan métodos de acceso y se burlan de llamadas a cabo utilizando métodos/Typemock. Ese no es el problema. El problema es probar cosas que no necesitan ser públicas o que no pueden ser públicas.

No quiero hacer público el código por el mero hecho de probar, ya que puede introducir lagunas de seguridad (solo publicar una interfaz para ocultar esto no es una opción porque cualquiera puede devolver el objeto a su tipo original y obtener acceso a cosas que no quisiera)

El código que se refactoriza a otra clase para la prueba está bien, pero puede perder contexto. Siempre he pensado que es una mala práctica tener clases de "ayuda" que pueden contener una olla de código sin contexto específico (pensando en SRP aquí). Realmente no creo que esto funcione para controladores de eventos tampoco.

Estoy feliz de que se demuestre que estoy equivocado. ¡No estoy seguro de cómo probar esta funcionalidad! Siempre he pensado que si se puede romper o cambiar, pruébelo.

Cheers, Jason

+0

¿Qué marco de aislamiento va a utilizar? –

+0

Estoy usando Typemock Isolator. Burlarse de las llamadas a los métodos es fácil. ¡Realmente no me gusta tener que hacerlo ya que cambia el comportamiento de la clase que se está probando! – Jason

+1

¡Todo esto me hace desear poder escribir mi propio marco desde el principio para poder diseñar estos problemas desde el principio! – Jason

Respuesta

3

En toda mi unidad de pruebas, nunca he molestado prueba private funciones. Por lo general, solo probé las funciones public.Esto va junto con la metodología Black Box Testing.

Tiene razón al decir que realmente no puede probar las funciones privadas a menos que expone la clase privada.

Si su "clase separada para la prueba" está en el mismo conjunto, puede optar por usar interna en lugar de privada. Esto expone los métodos internos a su código, pero estos métodos no serán accesibles para el código que no está en su ensamblaje.

EDITAR: buscando SO para este tema Encontré this question. La respuesta más votada es similar a mi respuesta.

+2

ahh, pero en realidad puedes probar clases privadas :). Debe archivarse en "¿pero debería?", Pero de hecho es posible. Y ha habido casos en que lo encontré útil. – Matt

+0

+1 Matt: ídem. –

0

Admitiré que, cuando recientemente escribí pruebas de unidades para C#, descubrí que muchos de los trucos que conocía para Java no se aplicaban realmente (en mi caso, estaba probando clases internas).

Por ejemplo 1, si puede simular/simular el controlador de recuperación de datos, puede obtener acceso a la devolución de llamada a través del falso. (La mayoría de los otros idiomas que conozco que usan callbacks también tienden a no hacerlos privados).

Por ejemplo 2 Me gustaría iniciar el evento para probar el controlador.

El ejemplo 3 es un ejemplo del patrón de plantilla que existe en otros idiomas. He visto dos maneras de hacer esto:

  1. Pruebe toda la clase de todos modos (o al menos partes relevantes de ella). Esto funciona particularmente en los casos en que la clase base abstracta viene con sus propias pruebas, o la clase general no es demasiado compleja. En Java podría hacer esto si estuviera escribiendo una extensión de AbstractList, por ejemplo. Este también puede ser el caso si el patrón de plantilla fue generado por refactorización.

  2. Extienda la clase de nuevo con ganchos adicionales que permiten llamar directamente a los métodos protegidos.

13

Como ha afirmado Chris, es una práctica estándar probar solo los métodos públicos. Esto se debe a que, como consumidor de ese objeto, solo le preocupa lo que está públicamente disponible para usted. Y, en teoría, las pruebas unitarias adecuadas con casos extremos ejercerán todas las dependencias de métodos privados que tengan.

Dicho esto, me parece que hay algunas veces donde las pruebas de unidad de escritura directamente contra métodos privados pueden ser extremadamente útiles, y más sucinto al explicar, a través de las pruebas unitarias, algunos de los escenarios más complejos o casos extremos que podrían ser encontrado.

Si ese es el caso, aún puede invocar métodos privados utilizando la reflexión.

MyClass obj = new MyClass(); 
MethodInfo methodInfo = obj.GetType().GetMethod("MethodName", BindingFlags.Instance | BindingFlags.NonPublic); 
object result = methodInfo.Invoke(obj, new object[] { "asdf", 1, 2 }); 
// assert your expected result against the one above 
+0

+1, eso es bastante dulce. Nunca pensé que el reflejo pudiera hacer eso. – Chris

+0

También puede hacer esto usando un acceso privado y (opcionalmente) pasando un objeto privado que envuelve su instancia real que es mucho más limpia. Creo que también previene cualquier problema con la reflexión cuando llegas a firmar tus clases (no me cites sobre eso, ¡nunca he tenido que hacerlo todavía!) – Jason

7

tenemos una regla que dice ciclomática que ningún método debe tener una complejidad ciclomática mayor que 5

me gusta esa regla.

El punto es que los métodos privados son detalles de implementación. Están sujetos a cambios/refactorización. Desea probar la interfaz pública.

Si tiene métodos privados con lógica compleja, considere refactorizarlos en una clase separada. Eso también puede ayudar a mantener baja la complejidad ciclomática. Otra opción es hacer el método interno y usar InternalsVisibleTo (mencionado en uno de los enlaces en la respuesta de Chris).

Las capturas suelen aparecer cuando tiene dependencias externas a las que se hace referencia en métodos privados. En la mayoría de los casos, puede utilizar técnicas como Dependency Injection para desacoplar sus clases. Para su ejemplo con el marco de terceros, eso podría ser difícil. Intentaré primero refactorizar el diseño para separar las dependencias de terceros. Si eso no es posible, considere usar Typemock Isolator. No lo he usado, pero su característica clave es poder "simular" métodos privados, estáticos, etc.

Las clases son recuadros negros. Pruébalos de esa manera.

EDITAR: Trataré de responder al comentario de Jason sobre mi respuesta y la edición de la pregunta original. En primer lugar, creo que SRP empuja hacia más clases, no lejos de ellos. Sí, es mejor evitar las clases de ayudantes del ejército suizo. Pero, ¿qué tal una clase diseñada para manejar operaciones asincrónicas? O una clase de recuperación de datos? ¿Son parte de la responsabilidad de la clase original o pueden separarse?

Por ejemplo, supongamos que mueve esta lógica a otra clase (que podría ser interna). Esa clase implementa un Asynchronous Design Pattern que permite al llamador elegir si el método se llama de forma síncrona o asincrónica. Las pruebas unitarias o las pruebas de integración se escriben con el método síncrono. Las llamadas asincrónicas usan un patrón estándar y tienen baja complejidad; no probamos eso (excepto en las pruebas de aceptación). Si la clase async es interna, use InternalsVisibleTo para probarla.

+2

Gracias por la respuesta - Debería haber mencionado que sí uso TypeMock Isolator, y burlarse de clases, clases de métodos no es un problema. Tampoco es un problema manejar el código privado. Lo que es un problema es que (tome el ejemplo 1) si me burlo de la clase que realiza una recuperación de datos: no puedo hacer que invoque la devolución de llamada. (Ejemplo 2) No puedo probar manejadores de eventos privados sin profundizar en los métodos privados de una clase. – Jason

4

en realidad sólo hay dos casos que debe considerar:

  1. el código privado se llama, directa o indirectamente de código público y
  2. el código privado es no llamada de código público.

En el primer caso, el código privado se prueba automáticamente mediante las pruebas que utilizan el código público que llama al código privado, por lo que no es necesario probar el código privado. Y en el segundo caso, el código privado no se puede llamar en absoluto, por lo tanto, debe eliminarse, no probarse.

Ergo: no es necesario probar explícitamente el código privado.

Tenga en cuenta que cuando hace TDD es imposible para código privado no probado incluso existir. Porque cuando haces TDD, la única forma en que se puede mostrar el código privado es mediante un Extracto {Método | Clase | ...} Refactorización del código público. Y las Refactorizaciones son, por definición, preservadoras del comportamiento y, por lo tanto, preservación de cobertura de prueba. Y la única forma en que puede aparecer el código público es como resultado de una prueba fallida. Si el código público solo puede aparecer como código ya probado como resultado de una prueba fallida, y el código privado solo puede aparecer como resultado de ser extraído del código público a través de una refactorización conservadora del comportamiento, se deduce que el código privado no probado nunca puede aparecer.

+0

No creo que sea cierto cuando consideras el primer o el segundo ejemplo que di.Está afirmando que el código privado solo puede existir como resultado directo del código público refactorizado, pero eso no explica cómo probar el código para los manejadores de eventos o las devoluciones de llamada asincrónicas. ¡Esos son los dos problemas reales que estoy teniendo y no puedo entender! – Jason

2

algunos puntos pertenecientes a un tipo TDD que ha estado golpeando alrededor en C#:

1) Si programa de interfaces entonces cualquier método de una clase que no está en la interfaz es efectivamente privada.Es posible que encuentre una mejor forma de promover la capacidad de prueba y una mejor manera de usar interfaces también. Pruebe estos como miembros públicos.

2) Esos pequeños métodos de ayuda pueden pertenecer más correctamente a alguna otra clase. Busque la envidia de las características. Lo que puede no ser razonable como miembro privado de la clase original (en la que lo encontró) puede ser un método público razonable de la clase que envidia. Pruebe estos en la nueva clase como miembros públicos.

3) Si examina una serie de pequeños métodos privados, puede encontrar que tienen cohesión. Pueden representar una clase de interés más pequeña separada de la clase original. Si es así, esa clase puede tener todos los métodos públicos, pero puede mantenerse como miembro privado de la clase original o quizás crearse y destruirse en funciones. Pruebe estos en la nueva clase como miembros públicos.

4) Puede obtener una clase "Testable" del original, en la cual es una tarea trivial crear un nuevo método público que no haga más que llamar al método antiguo y privado. La clase comprobable es parte del marco de prueba, y no es parte del código de producción, por lo que es bueno tener acceso especial. Pruébelo en el marco de prueba como si fuera público.

Todo esto hace que sea bastante trivial tener pruebas de los métodos que actualmente son métodos privados de ayuda, sin estropear la forma en que intellisense funciona.

0

No pruebe el código privado, o lo lamentará más adelante cuando sea el momento de refactorizar. Luego, harás como Joel y escribirás sobre cómo TDD es demasiado trabajo porque constantemente tienes que refactorizar tus pruebas con tu código.

Existen técnicas (burlas, stub) para realizar las pruebas adecuadas de caja negra. Búscalos.

1

Varias personas han respondido que los métodos privados no deberían probarse directamente, o deberían trasladarse a otra clase. Aunque creo que esto es bueno, a veces no vale la pena. Si bien estoy de acuerdo con esto en principio, he encontrado que esta es una de esas reglas que se pueden romper para ahorrar tiempo sin repercusiones negativas. Si la función es pequeña/simple, la sobrecarga de crear otra clase y clase de prueba es exagerada. Haré públicos estos métodos privados, pero luego no los agregaré a la interfaz. De esta forma, los consumidores de la clase (que deberían obtener la interfaz solo a través de mi biblioteca de IoC) no los usarán accidentalmente, pero están disponibles para las pruebas.

Ahora, en el caso de las devoluciones de llamada, este es un gran ejemplo donde hacer pública una propiedad privada puede hacer que las pruebas sean mucho más fáciles de escribir y mantener. Por ejemplo, si la clase A pasa una devolución de llamada a la clase B, convertiré esa devolución de llamada en una propiedad pública de la clase A. Una prueba para la clase A usa una implementación de stub para B que registra la devolución de llamada pasada. La prueba luego verifica la la devolución de llamada se pasa a B en las condiciones adecuadas. Una prueba separada para la clase A puede llamar directamente a la devolución de llamada, verificando que tiene los efectos secundarios apropiados.

Creo que este enfoque funciona muy bien para verificar comportamientos asincrónicos, lo he estado haciendo en algunas pruebas de JavaScript y algunas pruebas de lua. El beneficio es que tengo dos pequeñas pruebas simples (una que verifica que la devolución de llamada esté configurada, una que verifica que se comporte como se esperaba). Si intenta mantener la devolución de llamada en privado, entonces la prueba que verifica el comportamiento de devolución de llamada tiene mucha más configuración por hacer, y esa configuración se superpondrá con el comportamiento que debería estar en otras pruebas. Mal acoplamiento.

Lo sé, no es bonito, pero creo que funciona bien.

+0

Para su primera sugerencia, me resistiría a hacer esto, ya que con un simple casting puede introducir una gran retención de seguridad en su aplicación. Su sugerencia para el manejo de las devoluciones de llamada es casi tan lejos como obtuve con la prueba de métodos asíncronos ... terminé cambiando el código a privado y cambiando mis pruebas para probar la implementación privada utilizando accesadores al final, ya que no lo hice Quiero que el código se haga público. Hacerlo de esta manera solo tiene un poco de 'olor' :( – Jason

+0

¿Puedes expandir el agujero de seguridad relacionado con la fundición? –

+0

Hola Frank, Usando un poco de reflexión no es demasiado difícil calcular el tipo real detrás una interfaz. Entonces sería posible convertir el objeto que implementa la interfaz a su tipo original y obtener acceso a los métodos públicos a los que no tendría acceso a través de la interfaz. Según tengo entendido, (y en este caso) el El punto de una interfaz no es seguridad, es indicarle al desarrollador el uso correcto de la clase (o polimorfismo). – Jason

0

Esta es una pregunta que surge muy pronto al presentar las pruebas. La mejor técnica para resolver este problema es black-box test (como se menciona anteriormente) y siga el principio de responsabilidad única . Si cada una de sus clases solo tiene un motivo para cambiar, debería ser muy fácil probar su comportamiento sin tener que recurrir a sus métodos privados.

SRP - wikipedia/pdf

Esto también conduce a la robusta y adaptable código sea más como el principio de la responsabilidad individual es en realidad decir que su clase debe tener alta cohesion.

+0

¿Cómo abordaría el ejemplo 1 en este caso? –

+0

Tendría que ver el código, pero de inmediato diría que la recuperación de archivos no necesitaría saber que era asíncrona. Debe tener esa funcionalidad implementada por separado y una extensión de la misma que admita la transferencia asincrónica. En este caso, supongo que está utilizando todas las solicitudes asíncronas y los resultados de manejo en su devolución de llamada. Ambos están claramente separados ya. Uno envía, el otro recibe. Envío de prueba Prueba de recepción. Prueba ambos juntos. Más cosas se harán públicas, pero no dentro de su contexto actual. –

+0

Aquí hay un ejemplo de cómo estoy tratando de implementar su sugerencia (¡corríjanme si me equivoco!) Tengo una clase (C1) que puede obtener datos de manera asincrónica. Mi clase de llamada (C2) invoca la clase de recuperación de datos. La clase de llamada no tiene idea de cuánto tarda la llamada asincrónica (ya que es, por definición, en otra secuencia) La clase de recuperación de datos señala que notifica que ha recuperado datos. C2 luego extrae los datos de C1. Entonces, ¿cómo puedo probar esto en su totalidad sin introducir una condición de carrera? Probar el envío y la recepción de pruebas está bien, pero ¿cómo puedo probar ambas cosas juntas? – Jason

2

Aquí hay algunas buenas respuestas, y básicamente estoy de acuerdo con el consejo repetido de brotar nuevas clases. Para su ejemplo 3, sin embargo, hay una técnica sencilla astuto:

Ejemplo 3. Está utilizando un marco terceros que le permite extender anulando protegidas métodos virtuales (la ruta de acceso del público métodos a estos métodos virtuales se tratada generalmente como la programación cuadro negro y tendrán todo tipo de dependencias que el marco establece que usted no quiere saber acerca )

Digamos que MyClass extiende FrameworkClass. Haga que MyTestableClass amplíe MyClass, y luego proporcione métodos públicos en MyTestableClass que expongan los métodos protegidos de MyClass que necesita. No es una gran práctica, es una especie de habilitador del mal diseño, pero útil en ocasiones y muy simple.

0

En C# Se puede utilizar el atributo en AssemblyInfo.cs:

[assembly: InternalsVisibleTo("Worker.Tests")] 

Simplemente marque sus métodos privados con el interno y el proyecto de prueba seguirá viendo el método. ¡Sencillo! Tienes que mantener la encapsulación Y tener pruebas, sin todas las tonterías de TDD.

+0

La próxima vez, marque la pregunta como un duplicado en lugar de publicar varias respuestas idénticas. ¡Gracias! – Rob

Cuestiones relacionadas