software Dada donde ...¿Debería uno probar la implementación interna, o solo probar el comportamiento público?
- El sistema consiste en unos subsistemas
- Cada subsistema consta de unos 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 fuera 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 agregar nuevas funcionalidades), no es necesario alterar ninguna prueba automatizada existente: porque las pruebas solo dependen de la API pública del componente, y las API públicas suelen expandirse en lugar de modificarse.
Creo que esta política contrasta con un documento como Refactoring Test Code, que dice cosas como ...
- "... la unidad de pruebas ..."
- " ... una clase de prueba para cada clase en el sistema ... "
- " ... prueba de razón de código de código/producción ... está muy bien considerado para acercarse a una proporción de 1: 1 ..."
... todos con lo cual supongo que no estoy de acuerdo (o al menos no practico hielo).
Mi pregunta es, si no está de acuerdo con mi política, ¿podría explicar por qué? ¿En qué escenarios es este grado de prueba insuficiente?
En resumen:
- interfaces públicas son probados (y ensayarse), y rara vez cambian (que están añadido a, pero rara vez alterado)
- API internos están ocultos detrás de las API públicas, y pueden ser cambiado sin volver a escribir los casos de prueba que ponen a prueba las API públicas
nota al pie: algunos de mis casos de prueba '' son en realidad implementado como datos. Por ejemplo, los casos de prueba para la IU consisten en archivos de datos que contienen varias entradas de usuario y las salidas del sistema esperadas correspondientes. 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 el resultado esperado correspondiente.
Aunque rara vez necesito cambiar el código de prueba (porque generalmente las API públicas se agregan en lugar de cambiar), creo que a veces (por ejemplo, dos veces por semana) necesito cambiar algunos archivos de datos existentes. Esto puede ocurrir cuando modifico la salida del sistema para mejor (es decir, la nueva funcionalidad mejora la salida existente), lo que puede causar que una prueba existente 'falle' (porque el código de prueba solo intenta afirmar que la salida no ha cambiado).Para hacer frente a estos casos hago lo siguiente:
- Vuelva a ejecutar el conjunto de pruebas automatizado que una bandera en tiempo de ejecución especial, que le dice que no afirma la salida, pero en lugar de capturar la nueva salida en un nuevo directorio
- Utilice 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 correctos y esperados dada la nueva funcionalidad
- Actualice las pruebas existentes copiando los nuevos archivos de salida del nuevo directorio en el directorio desde el cual se ejecutan los casos de prueba (sobrescribiendo las pruebas anteriores)
Nota: por "componente", me refiero a algo así como "una DLL" o "un ensamblaje" ... algo que es lo suficientemente grande para ser visible en una arquitectura o diagrama de despliegue 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 se puede asignar a un equipo de desarrolladores (donde se asigna un componente diferente a un equipo diferente), y que por lo tanto, según Conway's Law que tiene una API pública relativamente estable.
Nota: El artículo Object-Oriented Testing: Myth and Reality dice,
Mito: las pruebas de caja Negro es suficiente. Si realiza un trabajo cuidadoso en el caso de prueba diseño utilizando la interfaz de clase o la especificación , puede estar seguro de que la clase se ha ejercido por completo. La prueba de caja blanca (mirando la implementación de un método para diseñar las pruebas ) viola el concepto de encapsulación .
Realidad: OO estructura importa, parte II. Muchos estudios han demostrado que de pruebas de recuadro negro piensa que son terriblemente a fondo por los desarrolladores único ejercicio de un tercio a un medio de los estados (y mucho caminos solos o estados) en la implementación bajo prueba. Hay tres razones para esto. En primer lugar, las entradas o los estados seleccionados normalmente tienen 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 seguro de que hay no hay comportamientos no especificados que necesitamos saber si alguna parte del sistema tiene no ha sido ejercida por el paquete de caja negra . La única forma de obtener esta información es mediante el código instrumentación. En tercer lugar, a menudo es difícil de ejercer excepción y error-handling sin examen de 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 dirigen la API pública) para ejercitar las diversas ramas de código (detalles de la característica implementación).
Esto empieza a parecerse a un duplicado de http: // stackoverflow .com/questions/182325/why-are-functional-tests-not-enough-what-do-unit-tests-offer - verifique si esa pregunta aborda lo que está buscando. – darch
@darch Sin duda está cerca si no es un duplicado; Gracias por mencionarlo. La respuesta aceptada en ese tema es que lo bueno de las pruebas unitarias es que son repetibles/automáticas: en mi caso, he automatizado mis pruebas funcionales para que sean repetibles. – ChrisW