¿Cómo detecta problemas de dependencia con las pruebas unitarias cuando usa objetos simulados?

98

Tiene una clase X y escribe algunas pruebas unitarias que verifican el comportamiento X1. También hay una clase A que toma X como una dependencia.

Cuando escribe pruebas unitarias para A, se burla de X. En otras palabras, mientras prueba unitariamente A, establece (postula) que el comportamiento del simulacro de X sea X1. El tiempo pasa, la gente usa su sistema, necesita cambios, X evoluciona: usted modifica X para mostrar el comportamiento X2. Obviamente, las pruebas unitarias para X fallarán y deberá adaptarlas.

¿Pero qué pasa con A? Las pruebas unitarias para A no fallarán cuando se modifique el comportamiento de X (debido a la burla de X). ¿Cómo detectar que el resultado de A será diferente cuando se ejecute con la X "real" (modificada)?

Espero respuestas en la línea de: "Ese no es el propósito de las pruebas unitarias", pero ¿qué valor tienen entonces las pruebas unitarias? ¿Realmente solo te dice que cuando pasan todas las pruebas, no has introducido un cambio radical? Y cuando el comportamiento de alguna clase cambia (voluntaria o involuntariamente), ¿cómo puede detectar (preferiblemente de manera automatizada) todas las consecuencias? ¿No deberíamos centrarnos más en las pruebas de integración?

bvgheluwe
fuente
36
Además de todas las respuestas sugeridas, debo decir que estoy en desacuerdo con la siguiente declaración: "¿Realmente solo te dice que cuando pasan todas las pruebas, no has introducido un cambio importante?" Si realmente crees que eliminar el miedo a la refactorización tiene poco valor, estás en la vía rápida hacia la escritura de código que no se puede
mantener
55
Las pruebas unitarias le indican si su unidad de código se comporta como espera. Ni mas ni menos. Los simulacros y los dobles de prueba proporcionan un entorno artificial y controlado para que pueda ejercer su unidad de código (de forma aislada) para ver si cumple con sus expectativas. Ni mas ni menos.
Robert Harvey
2
Creo que tu premisa es incorrecta. Cuando menciona X1que está diciendo que Ximplementa la interfaz X1. Si cambia la interfaz X1al X2simulacro que usó en las otras pruebas, ya no debería compilar, por lo tanto, se ve obligado a corregir esas pruebas también. Los cambios en el comportamiento de la clase no deberían importar. De hecho, su clase Ano debería depender de los detalles de implementación (que es lo que estaría cambiando en ese caso). Por lo tanto, las pruebas unitarias Atodavía son correctas y le dicen que Afunciona dada una implementación ideal de la interfaz.
Bakuriu
55
No sé sobre ti, pero cuando tengo que trabajar en una base de código sin pruebas, me muero de miedo y voy a romper algo. ¿Y por qué? Porque sucede con tanta frecuencia que algo se rompe cuando no estaba destinado. Y bendiga los corazones de nuestros probadores, no pueden probar todo. O incluso cerca. Pero una prueba de unidad felizmente se desarrollará a través de una rutina aburrida tras otra aburrida.
corsiKa

Respuestas:

125

Cuando escribes pruebas unitarias para A, te burlas de X

¿Vos si? No lo hago, a menos que sea absolutamente necesario. Tengo que si:

  1. X es lento o
  2. X tiene efectos secundarios

Si ninguno de estos aplica, entonces mi unidad de Apruebas Xtambién lo hará . Hacer cualquier otra cosa sería llevar las pruebas de aislamiento a un extremo ilógico.

Si tiene partes de su código utilizando simulacros de otras partes de su código, entonces estaría de acuerdo: ¿cuál es el punto de tales pruebas unitarias? Entonces no hagas esto. Deje que esas pruebas usen las dependencias reales, ya que forman pruebas mucho más valiosas de esa manera.

Y si algunas personas se enojan con usted llamando a estas pruebas, "pruebas unitarias", simplemente llámelas "pruebas automatizadas" y continúe escribiendo buenas pruebas automatizadas.

David Arno
fuente
94
@Laiv, No, se supone que las pruebas unitarias actúan como una unidad, es decir, que se ejecutan de forma aislada de otras pruebas . Los nodos y los gráficos pueden hacer una caminata. Si puedo realizar una prueba de extremo a extremo aislada y sin efectos secundarios en poco tiempo, esa es una prueba unitaria. En caso de que no le guste esa definición, llámela prueba automatizada y deje de escribir pruebas de basura para adaptarse a una semántica tonta.
David Arno
99
@DavidArno Hay, por desgracia, una definición muy amplia de aislado. A algunos les gustaría que una "unidad" incluyera el nivel medio y la base de datos. Pueden creer lo que quieran, pero lo más probable es que en un desarrollo de cualquier tamaño, las ruedas se salgan en poco tiempo a medida que el gerente de construcción lo tire. En general, si están aislados del ensamblaje (o equivalente), está bien. Nota: si codifica para interfaces, es mucho más fácil agregar burlas y DI más tarde.
Robbie Dee
13
Usted aboga por un tipo diferente de prueba en lugar de responder la pregunta. Lo cual es un punto válido, pero esta es una forma bastante discreta de hacerlo.
Phil Frost
17
@PhilFrost, para citarme a mí mismo: " Y si alguna gente se molesta con que llames estas pruebas," pruebas unitarias ", simplemente llámalas" pruebas automatizadas "y continúa escribiendo buenas pruebas automatizadas " . Escribe pruebas útiles, no pruebas tontas que simplemente cumplen con alguna definición aleatoria de una palabra. O, alternativamente, acepte que tal vez tenga su definición de "prueba de unidad" incorrecta y que haya dejado de usar simulacros porque la tiene mal. De cualquier manera, terminarás con mejores pruebas.
David Arno
30
Estoy con @DavidArno en este caso; mi estrategia de prueba cambió después de ver esta charla de Ian Cooper: vimeo.com/68375232 . Para resumirlo en una sola oración: No pruebes las clases . Probar comportamientos . Sus pruebas no deben tener conocimiento de las clases / métodos internos utilizados para implementar el comportamiento deseado; solo deben conocer la superficie pública de su API / biblioteca y deben probar eso. Si las pruebas tienen demasiado conocimiento, entonces está probando los detalles de implementación y sus pruebas se vuelven frágiles, junto con su implementación, y en realidad solo un ancla alrededor de su cuello.
Richiban
79

Necesitas ambos. Pruebas unitarias para verificar el comportamiento de cada una de sus unidades, y algunas pruebas de integración para asegurarse de que estén conectadas correctamente. El problema de depender solo de las pruebas de integración es la explosión combinatoria resultante de las interacciones entre todas sus unidades.

Digamos que tiene clase A, que requiere 10 pruebas unitarias para cubrir completamente todas las rutas. Luego tiene otra clase B, que también requiere 10 pruebas unitarias para cubrir todas las rutas que el código puede recorrer. Ahora, digamos en su aplicación, necesita alimentar la salida de A a B. Ahora su código puede tomar 100 rutas diferentes desde la entrada de A hasta la salida de B.

Con las pruebas unitarias, solo necesita 20 pruebas unitarias + 1 prueba de integración para cubrir completamente todos los casos.

Con las pruebas de integración, necesitará 100 pruebas para cubrir todas las rutas de código.

Aquí hay un muy buen video sobre las desventajas de confiar solo en las pruebas de integración Las pruebas integradas de JB Rainsberger son una estafa HD

Eterno21
fuente
1
Estoy seguro de que no es casualidad que el signo de interrogación sobre la eficacia de las pruebas de integración haya ido de la mano con las pruebas unitarias que cubren cada capa más.
Robbie Dee
16
Correcto, pero en ninguna parte tus pruebas de 20 unidades requieren burla. Si tiene sus 10 pruebas de A que cubren todo A y sus 10 pruebas que cubren todo B y también vuelve a probar el 25% de A como un bono, esto parece entre "bueno" y algo bueno. Burlarse de A en las pruebas de Bs parece activamente estúpido (a menos que realmente haya una razón por la cual A es un problema, por ejemplo, es la base de datos o trae una larga red de otras cosas)
Richard Tingle
99
No estoy de acuerdo con la idea de que una sola prueba de integración sea suficiente si desea una cobertura completa. Las reacciones de B a las salidas de A variarán en función de la salida; Si cambiar un parámetro en A cambia su salida, entonces B puede no manejarlo correctamente.
Matthieu M.
3
@ Eternal21: Mi punto era que a veces, el problema no está en el comportamiento individual, sino en una interacción inesperada. Es decir, cuando el pegamento entre A y B se comporta inesperadamente en algún escenario. Entonces, A y B actúan de acuerdo con las especificaciones, y el caso feliz funciona, pero en algunas entradas hay un error en el código de pegamento ...
Matthieu M.
1
@MatthieuM. Yo diría que esto está más allá del alcance de las Pruebas de Unidad. El código de cola se puede probar por sí mismo, mientras que las interacciones entre A y B a través del código de cola es una Prueba de integración. Cuando se encuentran casos extremos o errores específicos, se pueden agregar a las pruebas de unidad de código de pegamento y, en última instancia, se verifican en las pruebas de integración.
Andrew T Finnell
72

Cuando escribes pruebas unitarias para A, te burlas de X. En otras palabras, mientras pruebas unitaria A, estableces (postulas) el comportamiento de la simulación de X como X1. El tiempo pasa, la gente usa su sistema, necesita cambios, X evoluciona: usted modifica X para mostrar el comportamiento X2. Obviamente, las pruebas unitarias para X fallarán y deberá adaptarlas.

Woah, espera un momento. Las implicaciones de las pruebas para la falla de X son demasiado importantes para pasar por alto de esa manera.

Si cambiar la implementación de X de X1 a X2 rompe las pruebas unitarias para X, eso indica que ha realizado un cambio incompatible hacia atrás en el contrato X.

X2 no es una X, en el sentido de Liskov , por lo que debe pensar en otras formas de satisfacer las necesidades de sus partes interesadas (como la introducción de una nueva especificación Y, implementada por X2).

Para obtener información más profunda, vea Pieter Hinjens: El fin de las versiones de software , o Rich Hickey Simple Made Easy .

Desde la perspectiva de A, existe una condición previa de que el colaborador respete el contrato X. Y su observación es efectivamente que la prueba aislada para A no le garantiza que A reconozca a los colaboradores que violen el contrato X.

Revisar las pruebas integradas son una estafa ; en un nivel alto, se espera que tenga tantas pruebas aisladas como necesite para asegurarse de que X2 implemente el contrato X correctamente, y tantas pruebas aisladas como sea necesario para asegurarse de que A haga lo correcto con respuestas interesantes de una X, y un número menor de pruebas integradas para garantizar que X2 y A estén de acuerdo con lo que significa X.

Algunas veces verá esta distinción expresada como pruebas solitarias versus sociablepruebas; ver Jay Fields trabajando eficazmente con pruebas unitarias .

¿No deberíamos centrarnos más en las pruebas de integración?

Una vez más, ver que las pruebas integradas son una estafa: Rainsberger describe en detalle un ciclo de retroalimentación positiva que es común (en su experiencia) a proyectos que dependen de pruebas integradas (ortografía de notas). En resumen, sin las pruebas aisladas / solitarias que aplican presión al diseño , la calidad se degrada, lo que lleva a más errores y pruebas más integradas ...

También necesitará (algunas) pruebas de integración. Además de la complejidad introducida por múltiples módulos, la ejecución de estas pruebas tiende a tener más resistencia que las pruebas aisladas; es más eficiente iterar en verificaciones muy rápidas cuando el trabajo está en progreso, guardando las verificaciones adicionales para cuando cree que está "listo".

VoiceOfUnreason
fuente
8
Esta debería ser la respuesta aceptada. La pregunta describe una situación en la que el comportamiento de una clase se ha modificado de manera incompatible, pero aún parece exteriormente idéntico. El problema aquí radica en el diseño de la aplicación, no en las pruebas unitarias. La forma en que esta circunstancia quedaría atrapada en las pruebas es mediante una prueba de integración entre esas dos clases.
Nick Coad
1
"sin las pruebas aisladas / solitarias que aplican presión al diseño, la calidad se degrada". Creo que este es un punto importante. Junto con las comprobaciones de comportamiento, las pruebas unitarias tienen el efecto secundario de forzarlo a tener un diseño más modular.
MickaëlG
Supongo que todo esto es cierto, pero ¿cómo me ayuda esto si una dependencia externa introduce un cambio incompatible hacia atrás en el contrato X? Tal vez una clase de rendimiento de E / S en una biblioteca rompe la compatibilidad, y nos estamos burlando de X porque no queremos que las pruebas unitarias en CI dependan de E / S pesadas. Creo que el OP está pidiendo una prueba para esto, y no entiendo cómo esto responde la pregunta. ¿Cómo hacer una prueba para esto?
Gerrit
15

Permítanme comenzar diciendo que la premisa central de la pregunta es errónea.

Nunca está probando (o burlándose) implementaciones, está probando (y burlándose) interfaces .

Si tengo una clase X real que implementa la interfaz X1, puedo escribir un XM simulado que también cumpla con X1. Entonces mi clase A debe usar algo que implemente X1, que puede ser clase X o simulacro XM.

Ahora, supongamos que cambiamos X para implementar una nueva interfaz X2. Bueno, obviamente mi código ya no se compila. A requiere algo que implemente X1 y que ya no exista. El problema ha sido identificado y se puede solucionar.

Supongamos que en lugar de reemplazar X1, simplemente lo modificamos. Ahora la clase A está lista. Sin embargo, el simulador XM ya no implementa la interfaz X1. El problema ha sido identificado y se puede solucionar.


Toda la base para las pruebas unitarias y la burla es que usted escribe código que usa interfaces. Al consumidor de una interfaz no le importa cómo se implementa el código, solo que se cumple el mismo contrato (entradas / salidas).

Esto se rompe cuando sus métodos tienen efectos secundarios, pero creo que se puede excluir de forma segura ya que "no se puede probar ni burlar".

Vlad274
fuente
11
Esta respuesta hace muchas suposiciones que no necesitan sostenerse. En primer lugar, supone, más o menos, que estamos en C # o Java (o, más precisamente, que estamos en un lenguaje compilado, que el lenguaje tiene interfaces y que X implementa una interfaz; ninguno de estos debe ser cierto). En segundo lugar, se supone que cualquier cambio en el comportamiento o "contrato" de X requiere un cambio en la interfaz (tal como lo entiende el compilador) que X implementa. Esto es claramente no verdadera, incluso si nos encontramos en Java o C #; puede cambiar la implementación de un método sin cambiar su firma.
Mark Amery
66
@ MarkAmery Es cierto que la terminología de "interfaz" es más específica para C # o Java, pero creo que el punto es asumir un "contrato" de comportamiento definido (y si eso no está codificado, entonces es imposible detectarlo automáticamente). También tiene toda la razón de que una implementación se puede cambiar sin un cambio en el contrato. Pero un cambio en la implementación sin un cambio en la interfaz (o contrato) no debería afectar a ningún consumidor. Si el comportamiento de A depende de cómo se implemente la interfaz (o contrato), entonces es imposible (significativamente) realizar pruebas unitarias.
Vlad274
1
"También tiene toda la razón de que una implementación se puede cambiar sin un cambio en el contrato" , aunque también es cierto, este no es el punto que estaba tratando de hacer. Más bien, estoy afirmando una distinción entre el contrato (la comprensión de un programador de lo que se supone que debe hacer un objeto, tal vez especificado en la documentación) y la interfaz (una lista de firmas de métodos, entendidas por el compilador) y diciendo que el contrato puede ser cambiado sin cambiar la interfaz. Todas las funciones con la misma firma son intercambiables desde la perspectiva del sistema de tipos, ¡pero no en realidad!
Mark Amery
44
@ MarkAmery: No creo que Vlad esté usando la palabra "interfaz" en el mismo sentido que tú la estás usando; En la forma en que leo la respuesta, no se trata de interfaces en el sentido estrecho de C # / Java (es decir, un conjunto de firmas de métodos) sino en el sentido general de la palabra, como se usa, por ejemplo, en los términos "interfaz de programación de aplicaciones" o incluso " interfaz de usuario". [...]
Ilmari Karonen
66
@IlmariKaronen Si Vlad está usando "interfaz" para significar "contrato" en lugar de en el sentido estricto de C # / Java, entonces la declaración "Ahora, supongamos que cambiamos X para implementar una nueva interfaz X2. Bueno, obviamente mi código ya no se compila. " es simplemente falso, ya que puede cambiar un contrato sin cambiar las firmas de ningún método. Pero honestamente, creo que el problema aquí es que Vlad no está usando ninguno de los dos significados de manera consistente, sino que los está combinando , que es lo que conduce al camino de afirmar que cualquier cambio en el contrato X1 necesariamente causará un error de compilación sin darse cuenta de que eso es falso .
Mark Amery
9

Tomando sus preguntas por turno:

¿Qué valor tienen las pruebas unitarias?

Son baratos de escribir y ejecutar y obtienes comentarios tempranos. Si rompe X, descubrirá más o menos inmediatamente si tiene buenas pruebas. Ni siquiera considere escribir pruebas de integración a menos que haya probado todas sus capas (sí, incluso en la base de datos).

¿Realmente solo te dice que cuando pasan todas las pruebas, no has introducido un cambio radical?

Tener pruebas que pasan realmente podría decir muy poco. Es posible que no haya escrito suficientes pruebas. Es posible que no haya probado suficientes escenarios. La cobertura del código puede ayudar aquí, pero no es una bala de plata. Es posible que tenga pruebas que siempre pasan. Por lo tanto, el rojo es el primer paso a menudo ignorado del refactor rojo, verde.

Y cuando el comportamiento de alguna clase cambia (voluntaria o involuntariamente), ¿cómo puede detectar (preferiblemente de manera automatizada) todas las consecuencias?

Más pruebas, aunque las herramientas son cada vez mejores. PERO debe definir el comportamiento de la clase en una interfaz (ver más abajo). NB siempre habrá un lugar para las pruebas manuales en la cima de la pirámide de pruebas.

¿No deberíamos centrarnos más en las pruebas de integración?

Cada vez más pruebas de integración tampoco son la respuesta, son caras de escribir, ejecutar y mantener. Dependiendo de su configuración de compilación, su administrador de compilación puede excluirlos de todos modos, haciéndolos depender de un desarrollador que lo recuerde (¡nunca es algo bueno!).

He visto a los desarrolladores pasar horas tratando de arreglar pruebas de integración rotas que habrían encontrado en cinco minutos si tuvieran buenas pruebas unitarias. De lo contrario, intente simplemente ejecutar el software; eso es todo lo que le interesará a sus usuarios finales. No tiene sentido tener millones de pruebas unitarias que pasan si toda la casa de cartas se cae cuando el usuario ejecuta toda la suite.

Si desea asegurarse de que la clase A consuma la clase X de la misma manera, debe usar una interfaz en lugar de una concreción. Entonces, es más probable que se produzca un cambio importante en el momento de la compilación.

Robbie Dee
fuente
9

Eso es correcto.

Las pruebas unitarias están ahí para probar la funcionalidad aislada de una unidad, una comprobación a primera vista de que funciona según lo previsto y no contiene errores estúpidos.

Las pruebas unitarias no están ahí para probar que toda la aplicación funciona.

Lo que mucha gente olvida es que las pruebas unitarias son el medio más rápido y sucio de validar su código. Una vez que sepa que sus pequeñas rutinas funcionan, también debe ejecutar las pruebas de integración. Las pruebas unitarias en sí mismas son solo marginalmente mejores que ninguna prueba.

La razón por la que tenemos pruebas unitarias es que se supone que son baratas. Rápido para crear, ejecutar y mantener. Una vez que comienzas a convertirlos en pruebas de integración mínimas, te encuentras en un mundo de dolor. También podría realizar la prueba de integración completa e ignorar por completo las pruebas unitarias si va a hacer eso.

ahora, algunas personas piensan que una unidad no es solo una función en una clase, sino toda la clase en sí (incluido yo mismo). Sin embargo, todo esto hace que aumente el tamaño de la unidad, por lo que es posible que necesite menos pruebas de integración, pero aún así lo necesita. Todavía es imposible verificar que su programa haga lo que se supone que debe hacer sin un conjunto completo de pruebas de integración.

y luego, aún deberá ejecutar la prueba de integración completa en un sistema en vivo (o semi-vivo) para verificar que funcione con las condiciones que utiliza el cliente.

gbjbaanb
fuente
2

Las pruebas unitarias no prueban la exactitud de nada. Esto es cierto para todas las pruebas. Por lo general, las pruebas unitarias se combinan con un diseño basado en contratos (el diseño por contrato es otra forma de decirlo) y posiblemente pruebas de corrección automatizadas, si la corrección necesita ser verificada regularmente.

Si tiene contratos reales, que consisten en invariantes de clase, condiciones previas y condiciones de publicación, es posible demostrar la corrección jerárquicamente, basando la corrección de los componentes de nivel superior en los contratos de los componentes de nivel inferior. Este es el concepto fundamental detrás del diseño por contrato.

Frank Hileman
fuente
Las pruebas unitarias no prueban la exactitud de nada . No estoy seguro de entender esto, ¿las pruebas unitarias verifican sus propios resultados seguramente? ¿O quiso decir que un comportamiento no puede probarse como correcto ya que puede abarcar varias capas?
Robbie Dee
77
@RobbieDee supongo, quería decir que cuando se prueba para fac(5) == 120, usted ha no probado que fac()en efecto devolver el factorial de su argumento. Solo ha demostrado que fac()devuelve el factorial de cinco cuando pasa 5. E incluso eso no es seguro, ya fac()que posiblemente podría regresar 42en su lugar los primeros lunes después de un eclipse total en Tombuctú ... El problema aquí es que no puede probar el cumplimiento verificando las entradas de prueba individuales, necesitaría verificar todas las entradas posibles, y también demuestre que no ha olvidado ninguno (como leer el reloj del sistema).
cmaster
1
@RobbieDee Las pruebas (incluidas las pruebas unitarias) son un mal sustituto, a menudo el mejor disponible, para el objetivo real, una prueba comprobada por máquina. Considere el espacio de estado completo de las unidades bajo prueba, incluido el espacio de estado de cualquier componente o simulación en el mismo. A menos que tenga un espacio de estado muy restringido, las pruebas no pueden cubrir ese espacio de estado. La cobertura completa sería una prueba, pero esto solo está disponible para pequeños espacios de estado, por ejemplo, probando un solo objeto que contiene un solo byte mutable o un entero de 16 bits. Las pruebas automatizadas son mucho más valiosas.
Frank Hileman
1
@cmaster Usted resumió muy bien la diferencia entre una prueba y una prueba. ¡Gracias!
Frank Hileman
2

Encuentro que las pruebas muy burladas rara vez son útiles. La mayoría de las veces, termino reimplementando el comportamiento que la clase original ya tiene, lo que anula totalmente el propósito de burlarse.

Para mí, una mejor estrategia es tener una buena separación de preocupaciones (por ejemplo, puede probar la Parte A de su aplicación sin incluir las partes B a Z). Una arquitectura tan buena realmente ayuda a escribir una buena prueba.

Además, estoy más que dispuesto a aceptar los efectos secundarios siempre que pueda revertirlos, por ejemplo, si mi método modifica los datos en la base de datos, ¡déjelo! Mientras pueda hacer que la base de datos vuelva al estado anterior, ¿cuál es el daño? Además, existe el beneficio de que mi prueba puede verificar si los datos se ven como se esperaba. Las bases de datos en memoria o las versiones de prueba específicas de dbs realmente ayudan aquí (por ejemplo, la versión de prueba en memoria de RavenDB).

Finalmente, me gusta hacer burlas en los límites del servicio, por ejemplo, no haga esa llamada http al servicio b, pero interceptemos e introduzcamos un apropiado

Christian Sauer
fuente
1

Desearía que las personas en ambos campos entiendan que las pruebas de clase y las pruebas de comportamiento no son ortogonales.

Las pruebas de clase y las pruebas unitarias se usan indistintamente y tal vez no deberían usarse. Algunas pruebas unitarias simplemente se implementan en clases. Eso es todo. Las pruebas unitarias se han realizado durante décadas en idiomas sin clases.

En cuanto a los comportamientos de prueba, es perfectamente posible hacerlo dentro de las pruebas de clase utilizando la construcción GWT.

Además, si sus pruebas automatizadas proceden a lo largo de las líneas de clase o comportamiento, más bien depende de sus prioridades. Algunos necesitarán hacer prototipos rápidamente y sacar algo por la puerta, mientras que otros tendrán restricciones de cobertura debido a los estilos de la casa. Muchas razones. Ambos son enfoques perfectamente válidos. Paga su dinero, toma su elección.

Entonces, qué hacer cuando el código se rompe. Si se ha codificado a una interfaz, solo la concreción debe cambiar (junto con cualquier prueba).

Sin embargo, la introducción de un nuevo comportamiento no tiene por qué comprometer el sistema en absoluto. Linux y otros están llenos de características obsoletas. Y cosas como los constructores (y los métodos) se pueden sobrecargar sin forzar a todos los códigos de llamada a cambiar.

Donde las pruebas de clase ganan es donde necesita hacer un cambio en una clase que aún no se ha conectado (debido a limitaciones de tiempo, complejidad o lo que sea). Es mucho más fácil comenzar con una clase si tiene pruebas exhaustivas.

Perro belga
fuente
Donde tenga concreción , habría implementado por escrito . ¿Es un calco de otro idioma (supongo que francés) o hay una distinción importante entre concreción e implementación ?
Peter Taylor
0

A menos que la interfaz para X haya cambiado, no necesita cambiar la prueba de unidad para A porque nada relacionado con A ha cambiado. Parece que realmente escribiste una prueba unitaria de X y A juntos, pero la llamaste prueba unitaria de A:

Cuando escribes pruebas unitarias para A, te burlas de X. En otras palabras, mientras pruebas unitaria A, estableces (postulas) el comportamiento de la simulación de X como X1.

Idealmente, la simulación de X debería simular todos los comportamientos posibles de X, no solo el comportamiento que espera de X. Entonces, sin importar lo que implemente realmente en X, A ya debería ser capaz de manejarlo. Por lo tanto, ningún cambio en X, aparte de cambiar la interfaz en sí, tendrá ningún efecto en la prueba unitaria de A.

Por ejemplo: supongamos que A es un algoritmo de clasificación y A proporciona datos para ordenar. La simulación de X debe proporcionar un valor de retorno nulo, una lista vacía de datos, un solo elemento, varios elementos ya ordenados, varios elementos aún no ordenados, múltiples elementos ordenados al revés, listas con el mismo elemento repetido, valores nulos entremezclados, ridículamente gran cantidad de elementos, y también debería arrojar una excepción.

Entonces, tal vez X inicialmente devolvió datos ordenados los lunes y listas vacías los martes. Pero ahora, cuando X devuelve datos sin clasificar los lunes y arroja excepciones los martes, a A no le importa, esos escenarios ya estaban cubiertos en la prueba de la unidad de A.

Disco Moby
fuente
¿Qué pasa si X acaba de cambiar el nombre del índice en la matriz devuelta de foobar a foobarRandomNumber, ¿cómo puedo contar con eso? si entiendes mi punto, este es básicamente mi problema, cambié el nombre de una columna devuelta de secondName por apellido, una tarea clásica, pero mi prueba nunca lo sabré, ya que es burlada. Simplemente tengo un sentimiento tan extraño como si muchas personas en esta pregunta nunca
hubieran
El compilador debería haber detectado este cambio y haberle dado un error del compilador. Si está usando algo como Javascript, entonces le recomiendo cambiar a Typecript o usar un compilador como Babel que pueda detectar estas cosas.
Moby Disk
¿Qué sucede si estoy usando matrices en PHP, o en Java o en Javascript, si cambia un índice de matriz o lo elimina, ninguno de esos compiladores de idiomas le informará sobre ello, el índice podría estar anidado en el número 36? nivel anidado de la matriz, por lo tanto, creo que el compilador no es la solución para esto.
FantomX1
-2

Tienes que mirar diferentes pruebas.

Las pruebas unitarias por sí mismas solo probarán X. Están allí para evitar que cambie el comportamiento de X pero no asegura todo el sistema. Se aseguran de que pueda refactorizar su clase sin introducir un cambio en el comportamiento. Y si rompes X, lo rompiste ...

De hecho, A debe simular X para sus pruebas unitarias y la prueba con el simulacro debe seguir aprobando incluso después de cambiarlo.

¡Pero hay más de un nivel de prueba! También hay pruebas de integración. Estas pruebas están ahí para validar la interacción entre las clases. Estas pruebas generalmente tienen un precio más alto ya que no usan simulacros para todo. Por ejemplo, una prueba de integración en realidad podría escribir un registro en una base de datos, y una prueba unitaria no debería tener dependencias externas.

Además, si X necesita tener un nuevo comportamiento, sería mejor proporcionar un nuevo método que proporcione el resultado deseado

Heiko Hatzfeld
fuente