Pruebas unitarias de componentes internos

14

¿Hasta qué punto prueba por unidad los componentes internos / privados de una clase / módulo / paquete / etc? ¿Los prueba o prueba la interfaz con el mundo exterior? Un ejemplo de estos métodos internos es privado.

Como ejemplo, imagine un analizador de descenso recursivo , que tiene varios procedimientos internos (funciones / métodos) llamados desde uno procedimiento central. La única interfaz con el mundo exterior es el procedimiento central, que toma una cadena y devuelve la información analizada. Los otros procedimientos analizan diferentes partes de la cadena, y se llaman desde el procedimiento central u otros procedimientos.

Naturalmente, debe probar la interfaz externa llamándola con cadenas de muestra y comparándola con la salida analizada a mano. Pero ¿qué pasa con los otros procedimientos? ¿Los probarías individualmente para comprobar que analizan sus subcadenas correctamente?

Puedo pensar en algunos argumentos:

Pros :

  1. Más pruebas siempre son mejores, y esto puede ayudar a aumentar la cobertura del código
  2. Algunos componentes internos pueden ser difíciles de dar entradas específicas (por ejemplo, casos de borde) al dar entrada a la interfaz externa
  3. Pruebas más claras. Si un componente interno tiene un error (fijo), un caso de prueba para ese componente deja en claro que el error estaba en ese componente específico

Contras :

  1. La refactorización se vuelve demasiado dolorosa y lenta. Para cambiar cualquier cosa, debe volver a escribir las pruebas de unidad, incluso si los usuarios de la interfaz externa no están afectados
  2. Algunos lenguajes y marcos de prueba no lo permiten

¿Cuáles son tus opiniones?

    
pregunta imgx64 04.11.2010 - 07:48

4 respuestas

8

Caso: un "módulo" (en un sentido amplio, es decir, algo que tiene una interfaz pública y posiblemente también algunas partes internas privadas) tiene alguna lógica complicada / complicada en su interior. La prueba solo de la interfaz del módulo será una especie de prueba de integración en relación con la estructura interna del módulo, y por lo tanto, en caso de que se encuentre un error, dicha prueba no localizará la parte / componente interno exacto responsable del fallo.

Solución: convierta las partes internas complicadas en módulos, pruébelas por unidad (y repita estos pasos si son demasiado complicadas) e impórtelas a su módulo original. Ahora tiene solo un conjunto de módulos lo suficientemente simples como para uniit-test (ambos comprueban que el comportamiento es correcto y corrigen los errores) fácilmente, y eso es todo.

Nota:

  • no habrá necesidad de cambiar nada en las pruebas de los "submódulos" (anteriores) del módulo al cambiar el contrato del módulo, a menos que el "submódulo" no ofrezca servicios suficientes para cumplir los Contrato nuevo / modificado.

  • nada se hará innecesariamente público , es decir, el contrato del módulo se mantendrá y la encapsulación se mantendrá.

[Update◆

Para probar alguna lógica interna inteligente en los casos en que es difícil colocar las partes internas del objeto (me refiero a los miembros que no son los módulos / paquetes importados de forma privada) en el estado adecuado con solo alimentar las entradas a través de la interfaz pública del objeto:

  • solo tiene un código de prueba con el acceso de amigos (en términos de C ++) o de paquetes (Java) a las entrañas que en realidad configuran el estado desde dentro y prueban el comportamiento como usted quisiera.

    • esto no volverá a interrumpir la encapsulación al tiempo que proporciona un acceso directo y sencillo a las partes internas para fines de prueba; simplemente ejecute las pruebas como una "caja negra" y compílelas en las versiones de lanzamiento.
respondido por el mlvljr 04.11.2010 - 13:40
2

El enfoque del código basado en FSM es un poco diferente del utilizado tradicionalmente. Es muy similar a lo que se describe aquí para las pruebas de hardware (que normalmente también son FSM).

En resumen, creas una secuencia de entrada de prueba (o un conjunto de secuencias de entrada de prueba) que no solo debe producir una salida determinada, sino que también cuando se produce una salida "mala" en particular permite identificar el componente fallido por la naturaleza de la falla . El enfoque es bastante escalable, cuanto más tiempo dedique al diseño de la prueba, mejor será la prueba.

Este tipo de prueba está más cerca de lo que se denomina "pruebas funcionales", pero elimina la necesidad de cambiar las pruebas cada vez que toca ligeramente una implementación.

    
respondido por el bobah 04.11.2010 - 09:39
2

Bueno - depende :-). Si está siguiendo un enfoque BDD (Behavior Driven Development) o ATDD (Acceptance Test Driven Development), entonces la prueba de la interfaz pública está bien (siempre que la pruebe exhaustivamente con diferentes entradas. La implementación subyacente, por ejemplo, los métodos privados no lo es. realmente importante.

Sin embargo, digamos que desea que parte de ese algoritmo se ejecute dentro de un cierto período de tiempo o a lo largo de una cierta curva de BigO (por ejemplo, nlogn), entonces sí, la prueba de las partes individuales es importante. Algunos dirían que se trata más de un enfoque tradicional de TDD / prueba de unidad.

Como con todo, YMMV

    
respondido por el Martijn Verburg 04.11.2010 - 10:36
1

Divídalo en varias partes con un significado funcional, por ejemplo, ParseQuotedString() , ParseExpression() , ParseStatement() , ParseFile() y haga que todas sean públicas. ¿Qué tan probable es que la sintaxis cambie tanto que se vuelvan irrelevantes?

    
respondido por el DomQ 04.11.2010 - 09:04

Lea otras preguntas en las etiquetas