Hoy estaba viendo un video "Conceptos básicos de JUnit " y el autor dijo que al probar un método determinado en su programa, no debe usar otros métodos propios en el proceso.
Para ser más específico, estaba hablando de probar algún método de creación de registros que tomara un nombre y un apellido para los argumentos, y los usó para crear registros en una tabla dada. Pero afirmó que en el proceso de probar este método, no debería usar sus otros métodos DAO para consultar la base de datos para verificar el resultado final (para verificar que el registro se haya creado con los datos correctos). Afirmó que para eso, debería escribir código JDBC adicional para consultar la base de datos y verificar el resultado.
Creo que entiendo el espíritu de su afirmación: no desea que el caso de prueba de un método dependa de la corrección de los otros métodos (en este caso, el método DAO), y esto se logra escribiendo (nuevamente) su propia validación / código de soporte (que debería ser más específico y centrado, por lo tanto, un código más simple).
No obstante, las voces dentro de mi cabeza comenzaron a protestar con argumentos como la duplicación de código, esfuerzos adicionales innecesarios, etc. Quiero decir, si ejecutamos toda la batería de prueba y probamos todos nuestros métodos públicos a fondo (incluido el método DAO en este caso), debería ¿Está bien usar algunos de esos métodos mientras se prueban otros métodos? Si uno de ellos no está haciendo lo que se supone que debe hacer, entonces su propio caso de prueba fallará, y podemos arreglarlo y volver a ejecutar la batería de prueba. No es necesario duplicar el código (incluso si el código duplicado es algo más simple) ni desperdiciar esfuerzos.
Tengo un fuerte presentimiento sobre esto debido a varias aplicaciones recientes de Excel - VBA que he escrito (debidamente probadas por unidad gracias a Rubberduck para VBA ), donde aplicar esta recomendación significaría mucho trabajo adicional adicional, sin beneficio percibido.
¿Puedes por favor compartir tus ideas sobre esto?
fuente
Respuestas:
El espíritu de su reclamo es de hecho correcto. El objetivo de las pruebas unitarias es aislar el código, probarlo sin dependencias, para que cualquier comportamiento erróneo pueda reconocerse rápidamente donde está sucediendo.
Dicho esto, las pruebas unitarias son una herramienta, y están destinadas a servir a sus propósitos, no es un altar para rezar. A veces eso significa dejar dependencias porque funcionan de manera suficientemente confiable y no quieres molestarte en burlarte de ellas, a veces eso significa que algunas de las pruebas de tu unidad son bastante cercanas, si no pruebas de integración.
En última instancia, no se lo calificará, lo importante es el producto final del software que se está probando, pero solo tendrá que tener en cuenta cuándo está doblando las reglas y decidiendo cuándo valen la pena las compensaciones.
fuente
Creo que esto se reduce a la terminología. Para muchos, una "prueba unitaria" es algo muy específico y, por definición , no se puede tener una condición de aprobación / falla que dependa de cualquier código fuera de la unidad (método, función, etc.) que se esté probando. Esto incluiría la interacción con una base de datos.
Para otros, el término "prueba unitaria" es mucho más flexible y abarca cualquier tipo de prueba automatizada, incluido el código de prueba que prueba porciones integradas de la aplicación.
Un purista de prueba (si puedo usar ese término) podría llamar a eso una prueba de integración , distinguida de una prueba unitaria sobre la base de que la prueba depende de más de una unidad pura de código.
Sospecho que está utilizando la versión más flexible del término "prueba unitaria" y en realidad se refiere a una "prueba de integración".
Usando esas definiciones, no debe depender del código fuera de la unidad bajo prueba en una "prueba de unidad". Sin embargo, en una "prueba de integración", es perfectamente razonable escribir una prueba que ejercite una pieza específica de código y luego inspeccione la base de datos para los criterios de aprobación.
Si debe depender de pruebas de integración o pruebas unitarias o ambas es un tema de discusión mucho más amplio.
fuente
La respuesta es sí y no...
Las pruebas únicas que se realizan de forma aislada son imprescindibles, ya que le permiten establecer las bases para que su código de bajo nivel funcione correctamente. Pero al codificar bibliotecas más grandes, también encontrará áreas de código que requerirán pruebas que crucen unidades.
Estas pruebas de unidades cruzadas son buenas para la cobertura del código y cuando se prueban las funciones de extremo a extremo, pero vienen con algunos inconvenientes que debe tener en cuenta:
Al final del día, desea tener algunas de ambas ... muchas pruebas que atrapan la funcionalidad de bajo nivel y algunas que prueban la funcionalidad de extremo a extremo. En primer lugar, enfóquese en la razón por la que está escribiendo exámenes. Están ahí para darle confianza de que su código está funcionando de la manera que espera. Lo que sea que necesite hacer para que eso suceda está bien.
El hecho triste pero cierto es que si está utilizando pruebas automatizadas, ya tiene una ventaja sobre muchos desarrolladores. Las buenas prácticas de prueba son lo primero que se pierde cuando un equipo de desarrollo se enfrenta a plazos difíciles. Entonces, siempre y cuando se apegue a sus armas y escriba pruebas unitarias que sean un reflejo significativo de cómo debería funcionar su código, no me obsesionaría con cuán "puras" son sus pruebas.
fuente
Un problema con el uso de otros métodos de objeto para probar una condición es que se perderán los errores que se cancelan entre sí. Más importante aún, se está perdiendo el dolor de una implementación de prueba difícil, y ese dolor le está enseñando algo sobre su código subyacente. Las pruebas dolorosas ahora significan un mantenimiento doloroso más adelante.
Reduzca el tamaño de sus unidades, divida su clase, refactorice y rediseñe hasta que sea más fácil probar sin volver a implementar el resto de su objeto. Obtenga ayuda si cree que su código o sus pruebas no pueden simplificarse más. Trate de pensar en un colega que siempre parece tener suerte y obtenga tareas que sean fáciles de evaluar de manera limpia. Pídale ayuda, porque no es suerte.
fuente
Si la unidad de funcionalidad que se está probando es 'son los datos almacenados de forma persistente y recuperable', entonces tendría la prueba de prueba unitaria que: almacenar en una base de datos real, destruir cualquier objeto que tenga referencias a la base de datos, luego llamar al código para obtener el objetos de vuelta.
La prueba de si se crea un registro en la base de datos parece estar relacionada con los detalles de implementación del mecanismo de almacenamiento en lugar de probar la unidad de funcionalidad expuesta al resto del sistema.
Es posible que desee atajar la prueba para mejorar el rendimiento utilizando una base de datos simulada, pero he tenido contratistas que hicieron exactamente eso y se fueron al final de su contrato con un sistema que pasó tales pruebas, pero en realidad no almacenó nada en la base de datos entre reinicios del sistema.
Puede argumentar si 'unidad' en la prueba de unidad denota una 'unidad de código' o 'unidad de funcionalidad', funcionalidad tal vez creada por muchas unidades de código en concierto. No creo que sea una distinción útil: las preguntas que tendré en cuenta son: "¿la prueba le dice algo sobre si el sistema proporciona valor comercial" y "es la prueba frágil si la implementación cambia?" Las pruebas como la descrita son útiles cuando está TDD en el sistema: aún no ha escrito el 'obtener objeto del registro de la base de datos', por lo que no puede probar la unidad completa de funcionalidad, pero son frágiles contra los cambios de implementación, por lo que quítelos una vez que la operación completa sea comprobable.
fuente
El espíritu es correcto.
Idealmente, en una prueba unitaria , está probando una unidad (un método individual o una clase pequeña).
Idealmente, debería desconectar todo el sistema de base de datos. Es decir, ejecutaría su método en un entorno falso y solo se aseguraría de que llame a las API de DB correctas en el orden correcto. Explícitamente, positivamente, no desea probar la base de datos cuando pruebe uno de sus propios métodos.
Los beneficios son muchos. Sobre todo, las pruebas se vuelven cegadoras rápidamente porque no necesita preocuparse por configurar un entorno de base de datos correcto y revertirlo después.
Por supuesto, este es un objetivo elevado en cualquier proyecto de software que no lo haga desde el principio. Pero es el espíritu de las pruebas unitarias .
Tenga en cuenta que hay otras pruebas, como pruebas de características, pruebas de comportamiento, etc., que son diferentes de este enfoque; no confunda "pruebas" con "pruebas unitarias".
fuente
Hay al menos una razón muy importante para no utilizar el acceso directo a la base de datos en sus pruebas unitarias para el código comercial que interactúa con la base de datos: si cambia la implementación de su base de datos, tendrá que reescribir todas esas pruebas unitarias. Cambie el nombre de una columna, para su código solo tiene que cambiar una sola línea en la definición del mapeador de datos. Sin embargo, si no usa su mapeador de datos durante las pruebas, encontrará que también tiene que cambiar cada prueba de unidad que hace referencia a esta columna . Esto podría convertirse en una cantidad extraordinaria de trabajo, particularmente para cambios más complejos que son menos susceptibles de búsqueda y reemplazo.
Además, el uso de su mapeador de datos como un mecanismo abstracto en lugar de hablar directamente con la base de datos hace que sea más fácil eliminar por completo la dependencia de la base de datos , lo que podría no ser relevante ahora, pero cuando obtiene hasta miles de pruebas unitarias y todas están llegando a una base de datos, agradecerá que pueda refactorizarlos fácilmente para eliminar esa dependencia porque su conjunto de pruebas dejará de tomar minutos para ejecutarse y tomar segundos, lo que puede tener un gran beneficio en su productividad.
Su pregunta indica que está pensando en la línea correcta. Como sospecha, las pruebas unitarias también son código. Es tan importante hacerlos fáciles de mantener y adaptar a futuros cambios como para el resto de su código base. Esfuércese por mantenerlos legibles y eliminar la duplicación, y tendrá un conjunto de pruebas mucho mejor para ello. Y un buen conjunto de pruebas es uno que se acostumbra. Y un conjunto de pruebas que se usa ayuda a encontrar errores, mientras que uno que no se usa simplemente no tiene valor.
fuente
La mejor lección que aprendí cuando aprendí sobre pruebas unitarias y pruebas de integración es no probar métodos, sino probar comportamientos. En otras palabras, ¿qué hace este objeto?
Cuando lo veo de esa manera, un método que persiste en los datos y otro método que los lee de nuevo comienzan a ser comprobables. Si está probando los métodos específicamente, por supuesto, terminará con una prueba para cada método de forma aislada, como por ejemplo:
Este problema ocurre debido a la perspectiva de los métodos de prueba. No pruebes los métodos. Probar comportamientos. ¿Cuál es el comportamiento de la clase WidgetDao? Persiste los widgets. Ok, ¿cómo verificas que persiste los widgets? Bueno, ¿cuál es la definición de persistencia? Significa que cuando lo escribes, puedes volver a leerlo. Entonces leer + escribir juntos se convierte en una prueba, y en mi opinión, una prueba más significativa.
Esa es una prueba lógica, coherente, confiable y, en mi opinión, significativa.
Las otras respuestas se centran en la importancia del aislamiento, el debate pragmático versus el realista, y si una prueba unitaria puede o no consultar una base de datos. Sin embargo, esos no responden realmente la pregunta.
Para probar si algo se puede almacenar, debe almacenarlo y luego volver a leerlo. No puede probar si algo está almacenado si no tiene permiso para leerlo. No pruebe el almacenamiento de datos por separado de la recuperación de datos. Terminará con pruebas que no le dicen nada.
fuente
Un ejemplo de un caso de falla que preferiría detectar, es que el objeto bajo prueba usa una capa de almacenamiento en caché pero no persiste los datos según sea necesario. Luego, si consulta el objeto, dirá "sí, tengo el nuevo nombre y dirección", pero desea que la prueba falle porque en realidad no ha hecho lo que se suponía que debía hacer.
Alternativamente (y pasando por alto la violación de responsabilidad única), suponga que se requiere que persista una versión codificada en UTF-8 de la cadena en un campo orientado a bytes, pero en realidad persiste Shift JIS. Algún otro componente leerá la base de datos y espera ver UTF-8, de ahí el requisito. Luego, el viaje de ida y vuelta a través de este objeto informará el nombre y la dirección correctos porque lo convertirá de nuevo desde Shift JIS, pero su prueba no detecta el error. Es de esperar que sea detectado por alguna prueba de integración posterior, pero el objetivo de las pruebas unitarias es detectar los problemas temprano y saber exactamente qué componente es responsable.
No puede asumir esto, porque si no tiene cuidado, escribe un conjunto de pruebas mutuamente dependientes. El "¿salva?" prueba llama al método de guardar que está probando, y luego al método de carga para confirmar que está guardado. El "¿se carga?" test llama al método save para configurar el dispositivo de prueba y luego al método de carga que está probando para verificar el resultado. Ambas pruebas se basan en la exactitud del método que no están probando, lo que significa que ninguno de ellos prueba realmente la exactitud del método que está probando.
La pista de que hay un problema aquí es que dos pruebas que supuestamente están probando unidades diferentes en realidad hacen lo mismo . Ambos llaman a un setter seguido de un getter, luego verifican que el resultado sea el valor original. Pero quería probar que el setter persiste los datos, no que el par setter / getter trabaje en conjunto. Entonces, sabes que algo anda mal, solo tienes que averiguar qué y arreglar las pruebas.
Si su código está bien diseñado para pruebas unitarias, entonces hay al menos dos formas en que puede probar si los datos realmente han sido conservados correctamente por el método bajo prueba:
simule la interfaz de la base de datos y haga que su simulacro registre el hecho de que se han llamado a las funciones adecuadas con los valores esperados. Esto prueba que el método hace lo que se supone que debe hacer, y es la prueba unitaria clásica.
pasarle una base de datos real con exactamente la misma intención , para registrar si los datos se han conservado correctamente o no. Pero en lugar de tener una función simulada que simplemente dice "sí, obtuve los datos correctos", su prueba se lee directamente de la base de datos y confirma que es correcta. Puede que esta no sea la prueba más pura posible, porque un motor de base de datos completo es bastante importante para escribir un simulacro glorificado, con más posibilidades de que pase por alto alguna sutileza que haga pasar una prueba aunque algo esté mal (por ejemplo, yo no debería usar la misma conexión de base de datos para leer que se usó para escribir, porque podría ver una transacción no confirmada). Pero prueba lo correcto, y al menos sabes que precisamente implementa toda la interfaz de la base de datos sin tener que escribir ningún código simulado.
Por lo tanto, es un simple detalle de la implementación de la prueba si leo los datos de la base de datos de prueba por JDBC o si me burlo de la base de datos. De cualquier manera, el punto es que puedo probar la unidad mejor aislándola que si puedo permitir que conspire con otros métodos incorrectos en la misma clase para que se vea bien, incluso cuando algo está mal. Por lo tanto, quiero usar cualquier medio conveniente para verificar que los datos correctos persistieron, aparte de confiar en el componente cuyo método estoy probando.
Si su código no está bien diseñado para pruebas unitarias, es posible que no tenga otra opción, ya que el objeto cuyo método desea probar podría no aceptar la base de datos como una dependencia inyectada. En cuyo caso, la discusión sobre la mejor manera de aislar la unidad bajo prueba, se convierte en una discusión sobre qué tan cerca es posible aislar la unidad bajo prueba. Sin embargo, la conclusión es la misma. Si puede evitar conspiraciones entre unidades defectuosas, lo hace, sujeto al tiempo disponible y a cualquier otra cosa que piense que sería más eficaz para encontrar fallas en el código.
fuente
Así es como me gusta hacerlo. Esta es solo una elección personal ya que esto no afectará el resultado del producto, solo la forma de producirlo.
Esto depende de las posibilidades de poder agregar valores simulados en su base de datos sin la aplicación que está probando.
Digamos que tiene dos pruebas: A - leer la base de datos B - insertar en la base de datos (depende de A)
Si A pasa, entonces está seguro de que el resultado de B dependerá de la parte probada en B y no de las dependencias. Si A falla, puede tener un falso negativo para B. El error puede estar en B o en A. No puede estar seguro hasta que A devuelva un éxito.
No intentaría escribir algunas consultas en una prueba de unidad ya que no debería poder conocer la estructura de la base de datos detrás de la aplicación (o podría cambiar). Lo que significa que su caso de prueba podría fallar debido a su propio código. A menos que escriba una prueba para la prueba, pero quién evaluará la prueba para la prueba ...
fuente
Al final, todo se reduce a lo que quieres probar.
A veces es suficiente probar la interfaz proporcionada de su clase (por ejemplo, el registro se crea y almacena y se puede recuperar más tarde, tal vez después de un "reinicio"). En este caso, está bien (y generalmente es una buena idea) usar los métodos proporcionados para las pruebas. Esto permite que las pruebas se reutilicen, por ejemplo, si desea cambiar el mecanismo de almacenamiento (base de datos diferente, base de datos en memoria para pruebas, ...).
Por otro lado, en algunos casos tienes que verificar cómo las cosas persisten (¿quizás algún otro proceso se basa en que los datos estén en el formato correcto en esa base de datos?). Ahora no puede simplemente confiar en recuperar el registro correcto de la clase, sino que debe verificar que esté almacenado correctamente en la base de datos. Y la forma más directa de hacerlo es consultar la base de datos en sí.
fuente