Should one test internal implementation, or only test public behaviour

Software dado donde ...

El sistema consta de unos pocos subsistemas Cada subsistema consta de algunos componentes Cada componente se implementa utilizando muchas clases

... Me gusta escribir pruebas automatizadas de cada subsistema o componente.

No escribo una prueba para cada clase interna de un componente (excepto en la medida en que cada clase contribuye a la funcionalidad pública del componente y, por lo tanto, se puede probar / probar desde afuera a través de la API pública del componente).

Cuando refactorizo la implementación de un componente (que a menudo hago, como parte de la adición de una nueva funcionalidad), por lo tanto, no necesito alterar ninguna prueba automatizada existente: porque las pruebas solo dependen de la API pública del componente y del público Las API generalmente se expanden en lugar de modificarse.

Creo que esta política contrasta con un documento como Código de prueba de refactorización, que dice cosas como ...

"... examen de la unidad ..." "... una clase de prueba para cada clase en el sistema ..." "... la relación del código de prueba / código de producción ... se considera idealmente acercarse a una relación de 1: 1 ..."

... todo lo cual supongo que no estoy de acuerdo (o al menos no practico).

Mi pregunta es, si no está de acuerdo con mi política, ¿podría explicar por qué? ¿En qué escenarios es insuficiente este grado de prueba?

En resumen

Las interfaces públicas se prueban (y se vuelven a probar) y rara vez cambian (se agregan pero rara vez se modifican)as API internas están ocultas detrás de las API públicas, y se pueden cambiar sin volver a escribir los casos de prueba que prueban las API públicas

Footnote: algunos de mis 'casos de prueba' en realidad se implementan como datos. Por ejemplo, los casos de prueba para la interfaz de usuario consisten en archivos de datos que contienen varias entradas de usuario y las correspondientes salidas esperadas del sistema. Probar el sistema significa tener un código de prueba que lee cada archivo de datos, reproduce la entrada en el sistema y afirma que obtiene la salida esperada correspondiente.

Aunque rara vez necesito cambiar el código de prueba (porque las API públicas generalmente se agregan en lugar de cambiar), encuentro que a veces (por ejemplo, dos veces por semana) necesito cambiar algunos archivos de datos existentes. Esto puede suceder cuando cambio la salida del sistema para mejor (es decir, la nueva funcionalidad mejora la salida existente), lo que podría hacer que una prueba existente `` falle '' (porque el código de prueba solo intenta afirmar que la salida no ha cambiado). Para manejar estos casos hago lo siguiente:

Vuelva a ejecutar el conjunto de pruebas automatizadas con un indicador de tiempo de ejecución especial, que le indica que no confirme la salida, sino que capture la nueva salida en un nuevo directorioUtilice una herramienta de diferencia visual para ver qué archivos de datos de salida (es decir, qué casos de prueba) han cambiado, y para verificar que estos cambios sean buenos y como se esperaba dada la nueva funcionalidad Actualice las pruebas existentes copiando nuevos archivos de salida del nuevo directorio en el directorio desde el que se ejecutan los casos de prueba (sobrescribiendo las pruebas anteriores)

Footnote: por "componente", me refiero a algo como "una DLL" o "un ensamblaje" ... algo que es lo suficientemente grande como para ser visible en una arquitectura o un diagrama de implementación del sistema, a menudo implementado usando docenas o 100 clases, y con una API pública que consta de solo 1 o un puñado de interfaces ... algo que puede asignarse a un equipo de desarrolladores (donde un componente diferente se asigna a un equipo diferente), y que, por lo tanto, según Ley de Conway tener una API pública relativamente estable.

Footnote: El artículo Pruebas orientadas a objetos: mito y realidad dice

ito: las pruebas de recuadro negro son suficientes. Si realiza un trabajo cuidadoso de diseño de casos de prueba utilizando la interfaz de clase o la especificación, puede estar seguro de que la clase se ha ejercido completamente. Las pruebas de caja blanca (observando la implementación de un método para diseñar pruebas) violan el concepto mismo de encapsulación.

Reality: la estructura OO importa, parte II. Muchos estudios han demostrado que las series de pruebas de caja negra que los desarrolladores consideran insoportablemente exhaustivas solo ejercen de un tercio a la mitad de las declaraciones (por no hablar de rutas o estados) en la implementación bajo prueba. Hay tres razones para esto. Primero, las entradas o estados seleccionados típicamente ejercen rutas normales, pero no fuerzan todas las rutas / estados posibles. En segundo lugar, las pruebas de caja negra por sí solas no pueden revelar sorpresas. Supongamos que hemos probado todos los comportamientos especificados del sistema bajo prueba. Para estar seguros de que no hay comportamientos no especificados, necesitamos saber si alguna parte del sistema no ha sido ejercida por el conjunto de pruebas de caja negra. La única forma de obtener esta información es mediante instrumentación de código. En tercer lugar, a menudo es difícil ejercer una gestión de excepciones y errores sin examinar el código fuente.

Debo agregar que estoy haciendo pruebas funcionales de whitebox: veo el código (en la implementación) y escribo pruebas funcionales (que impulsan la API pública) para ejercer las diversas ramas de código (detalles de la implementación de la característica).

Respuestas a la pregunta(30)

Su respuesta a la pregunta